skedyul 1.4.5 → 1.4.7

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.d.ts CHANGED
@@ -15,7 +15,7 @@ declare const _default: {
15
15
  };
16
16
  export default _default;
17
17
  export { defineConfig, defineModel, defineChannel, definePage, defineWorkflow, defineAgent, defineEnv, defineNavigation, loadConfig, validateConfig, CONFIG_FILE_NAMES, getAllEnvKeys, getRequiredInstallEnvKeys, } from './config';
18
- export { queuedFetch, requeue, resolveQueue, createQueueHandle, queuedFetchResponse, registerQueueConfig, runWithRateLimitExecutionContext, getRateLimitExecutionContext, defaultShouldRetry, QueueContextError, QueueNotFoundError, QueuedFetchExhaustedError, RequeueOutsideContextError, RateLimitBackendError, } from './ratelimit';
18
+ export { queuedFetch, requeue, resolveQueue, createQueueHandle, queuedFetchResponse, registerQueueConfig, runWithRateLimitExecutionContext, getRateLimitExecutionContext, defaultShouldRetry, QueueContextError, QueueNotFoundError, QueuedFetchExhaustedError, RequeueOutsideContextError, RateLimitBackendError, RateLimitExceededError, } from './ratelimit';
19
19
  export type { QueueInput, QueueSelector, Lease, QueueLimits, ResolvedQueue, RateLimitExecutionContext, RateLimitBackend, QueueScope, QueueConfig, SerializableQueueConfig, QueueRegistry, } from './ratelimit';
20
20
  export { defineSchema, validateCRMSchema, parseCRMSchema, safeParseCRMSchema, } from './schemas';
21
21
  export { ThreadEventTypeSchema, CustomEventTypeSchema, EventTypeSchema, ParticipantKindSchema, BaseEventPayloadSchema, MessageEventPayloadSchema, ParticipantEventPayloadSchema, ContextChangedPayloadSchema, StatusChangedPayloadSchema, ScheduledEventPayloadSchema, AgentWorkflowEventPayloadSchema, CustomEventPayloadSchema, ThreadEventPayloadSchema, ThreadEventSchema, CreateThreadEventInputSchema, EventSubscriptionSchema, EventWaitSchema, EventsConfigSchema, } from './events';
package/dist/index.js CHANGED
@@ -174,6 +174,7 @@ __export(index_exports, {
174
174
  QueueNotFoundError: () => QueueNotFoundError,
175
175
  QueuedFetchExhaustedError: () => QueuedFetchExhaustedError,
176
176
  RateLimitBackendError: () => RateLimitBackendError,
177
+ RateLimitExceededError: () => RateLimitExceededError,
177
178
  RelationshipCardinalitySchema: () => RelationshipCardinalitySchema,
178
179
  RelationshipDefinitionSchema: () => RelationshipDefinitionSchema,
179
180
  RelationshipExtensionSchema: () => RelationshipExtensionSchema,
@@ -718,7 +719,9 @@ var ThreadEventTypeSchema = import_v43.z.enum([
718
719
  "thread.workflow.completed",
719
720
  // Scheduled events
720
721
  "thread.follow_up.due",
721
- "thread.reminder.due"
722
+ "thread.reminder.due",
723
+ // Signal events
724
+ "thread.signal.created"
722
725
  ]);
723
726
  var CustomEventTypeSchema = import_v43.z.string().regex(/^custom\.[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$/, {
724
727
  message: "Custom event type must match pattern: custom.{namespace}.{event}"
@@ -3696,6 +3699,57 @@ var AppAuthInvalidError = class extends Error {
3696
3699
  }
3697
3700
  };
3698
3701
 
3702
+ // src/ratelimit/errors.ts
3703
+ var QueueContextError = class extends Error {
3704
+ constructor(message) {
3705
+ super(message);
3706
+ this.code = "QUEUE_CONTEXT_ERROR";
3707
+ this.name = "QueueContextError";
3708
+ }
3709
+ };
3710
+ var QueueNotFoundError = class extends Error {
3711
+ constructor(queueName) {
3712
+ super(`Queue "${queueName}" is not defined in skedyul.config queues`);
3713
+ this.code = "QUEUE_NOT_FOUND";
3714
+ this.name = "QueueNotFoundError";
3715
+ }
3716
+ };
3717
+ var QueuedFetchExhaustedError = class extends Error {
3718
+ constructor(attempts, maxRetries, causeError) {
3719
+ super(
3720
+ `queuedFetch exhausted retries after ${attempts} attempts (maxRetries=${maxRetries})`
3721
+ );
3722
+ this.code = "QUEUED_FETCH_EXHAUSTED";
3723
+ this.name = "QueuedFetchExhaustedError";
3724
+ this.attempts = attempts;
3725
+ this.maxRetries = maxRetries;
3726
+ this.causeError = causeError;
3727
+ }
3728
+ };
3729
+ var RequeueOutsideContextError = class extends Error {
3730
+ constructor() {
3731
+ super("requeue() can only be called inside an active queuedFetch operation");
3732
+ this.code = "REQUEUE_OUTSIDE_CONTEXT";
3733
+ this.name = "RequeueOutsideContextError";
3734
+ }
3735
+ };
3736
+ var RateLimitBackendError = class extends Error {
3737
+ constructor(message, statusCode) {
3738
+ super(message);
3739
+ this.code = "RATE_LIMIT_BACKEND_ERROR";
3740
+ this.name = "RateLimitBackendError";
3741
+ this.statusCode = statusCode;
3742
+ }
3743
+ };
3744
+ var RateLimitExceededError = class extends Error {
3745
+ constructor(retryAfterMs, message = "Rate limit exceeded. Please try again later.") {
3746
+ super(message);
3747
+ this.code = "RATE_LIMITED";
3748
+ this.name = "RateLimitExceededError";
3749
+ this.retryAfterMs = retryAfterMs;
3750
+ }
3751
+ };
3752
+
3699
3753
  // src/server/context-logger.ts
3700
3754
  var import_async_hooks3 = require("async_hooks");
3701
3755
  var logContextStorage = new import_async_hooks3.AsyncLocalStorage();
@@ -3793,6 +3847,7 @@ function buildToolMetadata(registry) {
3793
3847
  const rawRetries = tool.retries ?? toolConfig.retries;
3794
3848
  const timeout = typeof rawTimeout === "number" && rawTimeout > 0 ? rawTimeout : 1e4;
3795
3849
  const retries = typeof rawRetries === "number" && rawRetries >= 1 ? rawRetries : 1;
3850
+ const queueTouchPoints = tool.queueTouchPoints ?? toolConfig.queueTouchPoints;
3796
3851
  return {
3797
3852
  name: tool.name,
3798
3853
  displayName: tool.label || tool.name,
@@ -3802,10 +3857,12 @@ function buildToolMetadata(registry) {
3802
3857
  // Include timeout/retries at top-level for tools/list response (used by syncExecutableTools)
3803
3858
  timeout,
3804
3859
  retries,
3860
+ queueTouchPoints,
3805
3861
  config: {
3806
3862
  timeout,
3807
3863
  retries,
3808
- completionHints: toolConfig.completionHints
3864
+ completionHints: toolConfig.completionHints,
3865
+ queueTouchPoints
3809
3866
  }
3810
3867
  };
3811
3868
  });
@@ -3948,6 +4005,27 @@ function createCallToolHandler(registry, state, onMaxRequests) {
3948
4005
  cursor: functionResult.cursor
3949
4006
  };
3950
4007
  } catch (error) {
4008
+ if (error instanceof RateLimitExceededError) {
4009
+ return {
4010
+ success: false,
4011
+ output: null,
4012
+ billing: { credits: 0 },
4013
+ meta: {
4014
+ success: false,
4015
+ message: error.message,
4016
+ toolName
4017
+ },
4018
+ error: {
4019
+ code: "RATE_LIMITED",
4020
+ message: error.message,
4021
+ category: "external"
4022
+ },
4023
+ retry: {
4024
+ allowed: true,
4025
+ afterMs: error.retryAfterMs
4026
+ }
4027
+ };
4028
+ }
3951
4029
  if (error instanceof AppAuthInvalidError) {
3952
4030
  return {
3953
4031
  output: null,
@@ -4311,7 +4389,8 @@ function serializeConfig(config) {
4311
4389
  description: tool.description,
4312
4390
  // Read timeout/retries from top-level first, then fallback to config
4313
4391
  timeout: tool.timeout ?? tool.config?.timeout,
4314
- retries: tool.retries ?? tool.config?.retries
4392
+ retries: tool.retries ?? tool.config?.retries,
4393
+ queueTouchPoints: tool.queueTouchPoints ?? tool.config?.queueTouchPoints
4315
4394
  })) : [],
4316
4395
  webhooks: webhookRegistry ? Object.values(webhookRegistry).map((w) => ({
4317
4396
  name: w.name,
@@ -5733,17 +5812,37 @@ var CONFIG_FILE_NAMES = [
5733
5812
  "skedyul.config.mjs",
5734
5813
  "skedyul.config.cjs"
5735
5814
  ];
5815
+ function loadTypeScriptConfigModule(absolutePath) {
5816
+ try {
5817
+ require("tsx/cjs");
5818
+ delete require.cache[absolutePath];
5819
+ const module2 = require(absolutePath);
5820
+ return module2.default ?? module2;
5821
+ } catch (error) {
5822
+ throw new Error(
5823
+ `Cannot load TypeScript config: ${absolutePath}: ${error instanceof Error ? error.message : String(error)}`
5824
+ );
5825
+ }
5826
+ }
5736
5827
  async function transpileTypeScript(filePath) {
5737
5828
  const content = fs3.readFileSync(filePath, "utf-8");
5738
5829
  const configDir = path3.dirname(path3.resolve(filePath));
5739
5830
  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*$/, "}");
5740
5831
  transpiled = transpiled.replace(
5741
5832
  /import\s+(\w+)\s+from\s+['"](\.[^'"]+)['"]\s*(?:with\s*\{[^}]*\})?/g,
5742
- (match, varName, relativePath) => {
5833
+ (_match, varName, relativePath) => {
5743
5834
  const absolutePath = path3.resolve(configDir, relativePath);
5744
5835
  return `const ${varName} = require('${absolutePath.replace(/\\/g, "/")}')`;
5745
5836
  }
5746
5837
  );
5838
+ transpiled = transpiled.replace(
5839
+ /import\s+\{\s*([^}]+)\s*\}\s+from\s+['"](\.[^'"]+)['"]\s*;?\n?/g,
5840
+ (_match, namedImports, relativePath) => {
5841
+ const absolutePath = path3.resolve(configDir, relativePath);
5842
+ return `const { ${namedImports.trim()} } = require('${absolutePath.replace(/\\/g, "/")}')
5843
+ `;
5844
+ }
5845
+ );
5747
5846
  transpiled = transpiled.replace(/import\s*\(\s*['"][^'"]+['"]\s*\)/g, "null");
5748
5847
  return transpiled;
5749
5848
  }
@@ -5754,16 +5853,9 @@ async function loadConfig(configPath) {
5754
5853
  }
5755
5854
  const isTypeScript = absolutePath.endsWith(".ts");
5756
5855
  try {
5757
- let moduleToLoad = absolutePath;
5758
5856
  if (isTypeScript) {
5759
- const transpiled = await transpileTypeScript(absolutePath);
5760
- const tempDir = os.tmpdir();
5761
- const tempFile = path3.join(tempDir, `skedyul-config-${Date.now()}.cjs`);
5762
- fs3.writeFileSync(tempFile, transpiled);
5763
- moduleToLoad = tempFile;
5764
5857
  try {
5765
- const module3 = require(moduleToLoad);
5766
- const config2 = module3.default || module3;
5858
+ const config2 = loadTypeScriptConfigModule(absolutePath);
5767
5859
  if (!config2 || typeof config2 !== "object") {
5768
5860
  throw new Error("Config file must export a configuration object");
5769
5861
  }
@@ -5771,16 +5863,31 @@ async function loadConfig(configPath) {
5771
5863
  throw new Error('Config must have a "name" property');
5772
5864
  }
5773
5865
  return config2;
5774
- } finally {
5866
+ } catch (tsxError) {
5867
+ const transpiled = await transpileTypeScript(absolutePath);
5868
+ const tempFile = path3.join(os.tmpdir(), `skedyul-config-${Date.now()}.cjs`);
5869
+ fs3.writeFileSync(tempFile, transpiled);
5775
5870
  try {
5776
- fs3.unlinkSync(tempFile);
5777
- } catch {
5871
+ const module3 = require(tempFile);
5872
+ const config2 = module3.default || module3;
5873
+ if (!config2 || typeof config2 !== "object") {
5874
+ throw new Error("Config file must export a configuration object");
5875
+ }
5876
+ if (!config2.name || typeof config2.name !== "string") {
5877
+ throw new Error('Config must have a "name" property');
5878
+ }
5879
+ return config2;
5880
+ } finally {
5881
+ try {
5882
+ fs3.unlinkSync(tempFile);
5883
+ } catch {
5884
+ }
5778
5885
  }
5779
5886
  }
5780
5887
  }
5781
5888
  const module2 = await import(
5782
5889
  /* webpackIgnore: true */
5783
- moduleToLoad
5890
+ absolutePath
5784
5891
  );
5785
5892
  const config = module2.default || module2;
5786
5893
  if (!config || typeof config !== "object") {
@@ -5831,49 +5938,6 @@ function getRequiredInstallEnvKeys(config) {
5831
5938
  return Object.entries(provision.env).filter(([, def]) => def.required).map(([key]) => key);
5832
5939
  }
5833
5940
 
5834
- // src/ratelimit/errors.ts
5835
- var QueueContextError = class extends Error {
5836
- constructor(message) {
5837
- super(message);
5838
- this.code = "QUEUE_CONTEXT_ERROR";
5839
- this.name = "QueueContextError";
5840
- }
5841
- };
5842
- var QueueNotFoundError = class extends Error {
5843
- constructor(queueName) {
5844
- super(`Queue "${queueName}" is not defined in skedyul.config queues`);
5845
- this.code = "QUEUE_NOT_FOUND";
5846
- this.name = "QueueNotFoundError";
5847
- }
5848
- };
5849
- var QueuedFetchExhaustedError = class extends Error {
5850
- constructor(attempts, maxRetries, causeError) {
5851
- super(
5852
- `queuedFetch exhausted retries after ${attempts} attempts (maxRetries=${maxRetries})`
5853
- );
5854
- this.code = "QUEUED_FETCH_EXHAUSTED";
5855
- this.name = "QueuedFetchExhaustedError";
5856
- this.attempts = attempts;
5857
- this.maxRetries = maxRetries;
5858
- this.causeError = causeError;
5859
- }
5860
- };
5861
- var RequeueOutsideContextError = class extends Error {
5862
- constructor() {
5863
- super("requeue() can only be called inside an active queuedFetch operation");
5864
- this.code = "REQUEUE_OUTSIDE_CONTEXT";
5865
- this.name = "RequeueOutsideContextError";
5866
- }
5867
- };
5868
- var RateLimitBackendError = class extends Error {
5869
- constructor(message, statusCode) {
5870
- super(message);
5871
- this.code = "RATE_LIMIT_BACKEND_ERROR";
5872
- this.name = "RateLimitBackendError";
5873
- this.statusCode = statusCode;
5874
- }
5875
- };
5876
-
5877
5941
  // src/ratelimit/resolve-queue-key.ts
5878
5942
  function resolveEndpointHandle(config, invocation) {
5879
5943
  if (config.endpoint) {
@@ -6317,11 +6381,30 @@ async function executeWithRetries(operation, maxRetries) {
6317
6381
  const timeoutMs = operation.resolved.config.timeout ?? 12e4;
6318
6382
  const retryDelayMs = operation.resolved.config.retryDelayMs ?? 1e3;
6319
6383
  const shouldRetryFn = getQueueConfigWithRetry(operation.resolved.name)?.shouldRetry ?? defaultShouldRetry;
6320
- const lease = await backend.acquire(
6321
- operation.resolved.queueKey,
6322
- operation.resolved.limits,
6323
- timeoutMs
6324
- );
6384
+ let lease;
6385
+ try {
6386
+ lease = await backend.acquire(
6387
+ operation.resolved.queueKey,
6388
+ operation.resolved.limits,
6389
+ timeoutMs
6390
+ );
6391
+ } catch (acquireError) {
6392
+ if (acquireError instanceof RateLimitBackendError && acquireError.statusCode === 408) {
6393
+ const retryAfterMs = Math.max(
6394
+ operation.resolved.limits.minTime ?? 1e3,
6395
+ operation.resolved.config.retryDelayMs ?? 1e3
6396
+ );
6397
+ throw new RateLimitExceededError(retryAfterMs);
6398
+ }
6399
+ if (acquireError instanceof Error && acquireError.message.includes("timed out")) {
6400
+ const retryAfterMs = Math.max(
6401
+ operation.resolved.limits.minTime ?? 1e3,
6402
+ operation.resolved.config.retryDelayMs ?? 1e3
6403
+ );
6404
+ throw new RateLimitExceededError(retryAfterMs);
6405
+ }
6406
+ throw acquireError;
6407
+ }
6325
6408
  operation.lease = lease;
6326
6409
  setActiveQueuedOperationLease(lease);
6327
6410
  try {
@@ -7893,6 +7976,7 @@ var index_default = { z: import_v413.z };
7893
7976
  QueueNotFoundError,
7894
7977
  QueuedFetchExhaustedError,
7895
7978
  RateLimitBackendError,
7979
+ RateLimitExceededError,
7896
7980
  RelationshipCardinalitySchema,
7897
7981
  RelationshipDefinitionSchema,
7898
7982
  RelationshipExtensionSchema,
@@ -25,3 +25,9 @@ export declare class RateLimitBackendError extends Error {
25
25
  readonly statusCode?: number;
26
26
  constructor(message: string, statusCode?: number);
27
27
  }
28
+ /** Thrown when queuedFetch cannot acquire a rate-limit slot within the queue timeout. */
29
+ export declare class RateLimitExceededError extends Error {
30
+ readonly code = "RATE_LIMITED";
31
+ readonly retryAfterMs: number;
32
+ constructor(retryAfterMs: number, message?: string);
33
+ }
@@ -1,6 +1,6 @@
1
1
  export { queuedFetch, requeue, resolveQueue, createQueueHandle, queuedFetchResponse, } from './queued-fetch';
2
2
  export type { QueueInput, QueueSelector, Lease, QueueLimits, ResolvedQueue, RateLimitExecutionContext, RateLimitBackend, } from './types';
3
- export { QueueContextError, QueueNotFoundError, QueuedFetchExhaustedError, RequeueOutsideContextError, RateLimitBackendError, } from './errors';
3
+ export { QueueContextError, QueueNotFoundError, QueuedFetchExhaustedError, RequeueOutsideContextError, RateLimitBackendError, RateLimitExceededError, } from './errors';
4
4
  export { registerQueueConfig, clearRegisteredQueueConfig, getQueueDefinitions, getQueueConfig, } from './config-loader';
5
5
  export { runWithRateLimitExecutionContext, getRateLimitExecutionContext, } from './context';
6
6
  export { resolveQueueKey, toQueueLimits } from './resolve-queue-key';
@@ -539,6 +539,7 @@ export declare const AgentYAMLV3Schema: z.ZodObject<{
539
539
  "thread.workflow.completed": "thread.workflow.completed";
540
540
  "thread.follow_up.due": "thread.follow_up.due";
541
541
  "thread.reminder.due": "thread.reminder.due";
542
+ "thread.signal.created": "thread.signal.created";
542
543
  }>, z.ZodString]>>>;
543
544
  condition: z.ZodOptional<z.ZodString>;
544
545
  emits: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodEnum<{
@@ -555,6 +556,7 @@ export declare const AgentYAMLV3Schema: z.ZodObject<{
555
556
  "thread.workflow.completed": "thread.workflow.completed";
556
557
  "thread.follow_up.due": "thread.follow_up.due";
557
558
  "thread.reminder.due": "thread.reminder.due";
559
+ "thread.signal.created": "thread.signal.created";
558
560
  }>, z.ZodString]>>>;
559
561
  waits: z.ZodOptional<z.ZodArray<z.ZodObject<{
560
562
  event: z.ZodUnion<readonly [z.ZodEnum<{
@@ -571,6 +573,7 @@ export declare const AgentYAMLV3Schema: z.ZodObject<{
571
573
  "thread.workflow.completed": "thread.workflow.completed";
572
574
  "thread.follow_up.due": "thread.follow_up.due";
573
575
  "thread.reminder.due": "thread.reminder.due";
576
+ "thread.signal.created": "thread.signal.created";
574
577
  }>, z.ZodString]>;
575
578
  timeout: z.ZodOptional<z.ZodString>;
576
579
  onTimeout: z.ZodOptional<z.ZodString>;
@@ -589,6 +592,7 @@ export declare const AgentYAMLV3Schema: z.ZodObject<{
589
592
  "thread.workflow.completed": "thread.workflow.completed";
590
593
  "thread.follow_up.due": "thread.follow_up.due";
591
594
  "thread.reminder.due": "thread.reminder.due";
595
+ "thread.signal.created": "thread.signal.created";
592
596
  }>, z.ZodString]>>>;
593
597
  cancelCondition: z.ZodOptional<z.ZodString>;
594
598
  }, z.core.$strip>>;
@@ -76,7 +76,9 @@ var ThreadEventTypeSchema = import_v4.z.enum([
76
76
  "thread.workflow.completed",
77
77
  // Scheduled events
78
78
  "thread.follow_up.due",
79
- "thread.reminder.due"
79
+ "thread.reminder.due",
80
+ // Signal events
81
+ "thread.signal.created"
80
82
  ]);
81
83
  var CustomEventTypeSchema = import_v4.z.string().regex(/^custom\.[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$/, {
82
84
  message: "Custom event type must match pattern: custom.{namespace}.{event}"
@@ -23,7 +23,9 @@ var ThreadEventTypeSchema = z.enum([
23
23
  "thread.workflow.completed",
24
24
  // Scheduled events
25
25
  "thread.follow_up.due",
26
- "thread.reminder.due"
26
+ "thread.reminder.due",
27
+ // Signal events
28
+ "thread.signal.created"
27
29
  ]);
28
30
  var CustomEventTypeSchema = z.string().regex(/^custom\.[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$/, {
29
31
  message: "Custom event type must match pattern: custom.{namespace}.{event}"
package/dist/server.js CHANGED
@@ -307,6 +307,16 @@ var AppAuthInvalidError = class extends Error {
307
307
  }
308
308
  };
309
309
 
310
+ // src/ratelimit/errors.ts
311
+ var RateLimitExceededError = class extends Error {
312
+ constructor(retryAfterMs, message = "Rate limit exceeded. Please try again later.") {
313
+ super(message);
314
+ this.code = "RATE_LIMITED";
315
+ this.name = "RateLimitExceededError";
316
+ this.retryAfterMs = retryAfterMs;
317
+ }
318
+ };
319
+
310
320
  // src/server/context-logger.ts
311
321
  var import_async_hooks3 = require("async_hooks");
312
322
  var logContextStorage = new import_async_hooks3.AsyncLocalStorage();
@@ -404,6 +414,7 @@ function buildToolMetadata(registry) {
404
414
  const rawRetries = tool.retries ?? toolConfig.retries;
405
415
  const timeout = typeof rawTimeout === "number" && rawTimeout > 0 ? rawTimeout : 1e4;
406
416
  const retries = typeof rawRetries === "number" && rawRetries >= 1 ? rawRetries : 1;
417
+ const queueTouchPoints = tool.queueTouchPoints ?? toolConfig.queueTouchPoints;
407
418
  return {
408
419
  name: tool.name,
409
420
  displayName: tool.label || tool.name,
@@ -413,10 +424,12 @@ function buildToolMetadata(registry) {
413
424
  // Include timeout/retries at top-level for tools/list response (used by syncExecutableTools)
414
425
  timeout,
415
426
  retries,
427
+ queueTouchPoints,
416
428
  config: {
417
429
  timeout,
418
430
  retries,
419
- completionHints: toolConfig.completionHints
431
+ completionHints: toolConfig.completionHints,
432
+ queueTouchPoints
420
433
  }
421
434
  };
422
435
  });
@@ -559,6 +572,27 @@ function createCallToolHandler(registry, state, onMaxRequests) {
559
572
  cursor: functionResult.cursor
560
573
  };
561
574
  } catch (error) {
575
+ if (error instanceof RateLimitExceededError) {
576
+ return {
577
+ success: false,
578
+ output: null,
579
+ billing: { credits: 0 },
580
+ meta: {
581
+ success: false,
582
+ message: error.message,
583
+ toolName
584
+ },
585
+ error: {
586
+ code: "RATE_LIMITED",
587
+ message: error.message,
588
+ category: "external"
589
+ },
590
+ retry: {
591
+ allowed: true,
592
+ afterMs: error.retryAfterMs
593
+ }
594
+ };
595
+ }
562
596
  if (error instanceof AppAuthInvalidError) {
563
597
  return {
564
598
  output: null,
@@ -922,7 +956,8 @@ function serializeConfig(config) {
922
956
  description: tool.description,
923
957
  // Read timeout/retries from top-level first, then fallback to config
924
958
  timeout: tool.timeout ?? tool.config?.timeout,
925
- retries: tool.retries ?? tool.config?.retries
959
+ retries: tool.retries ?? tool.config?.retries,
960
+ queueTouchPoints: tool.queueTouchPoints ?? tool.config?.queueTouchPoints
926
961
  })) : [],
927
962
  webhooks: webhookRegistry ? Object.values(webhookRegistry).map((w) => ({
928
963
  name: w.name,
@@ -268,6 +268,16 @@ var AppAuthInvalidError = class extends Error {
268
268
  }
269
269
  };
270
270
 
271
+ // src/ratelimit/errors.ts
272
+ var RateLimitExceededError = class extends Error {
273
+ constructor(retryAfterMs, message = "Rate limit exceeded. Please try again later.") {
274
+ super(message);
275
+ this.code = "RATE_LIMITED";
276
+ this.name = "RateLimitExceededError";
277
+ this.retryAfterMs = retryAfterMs;
278
+ }
279
+ };
280
+
271
281
  // src/server/context-logger.ts
272
282
  import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
273
283
  var logContextStorage = new AsyncLocalStorage3();
@@ -365,6 +375,7 @@ function buildToolMetadata(registry) {
365
375
  const rawRetries = tool.retries ?? toolConfig.retries;
366
376
  const timeout = typeof rawTimeout === "number" && rawTimeout > 0 ? rawTimeout : 1e4;
367
377
  const retries = typeof rawRetries === "number" && rawRetries >= 1 ? rawRetries : 1;
378
+ const queueTouchPoints = tool.queueTouchPoints ?? toolConfig.queueTouchPoints;
368
379
  return {
369
380
  name: tool.name,
370
381
  displayName: tool.label || tool.name,
@@ -374,10 +385,12 @@ function buildToolMetadata(registry) {
374
385
  // Include timeout/retries at top-level for tools/list response (used by syncExecutableTools)
375
386
  timeout,
376
387
  retries,
388
+ queueTouchPoints,
377
389
  config: {
378
390
  timeout,
379
391
  retries,
380
- completionHints: toolConfig.completionHints
392
+ completionHints: toolConfig.completionHints,
393
+ queueTouchPoints
381
394
  }
382
395
  };
383
396
  });
@@ -520,6 +533,27 @@ function createCallToolHandler(registry, state, onMaxRequests) {
520
533
  cursor: functionResult.cursor
521
534
  };
522
535
  } catch (error) {
536
+ if (error instanceof RateLimitExceededError) {
537
+ return {
538
+ success: false,
539
+ output: null,
540
+ billing: { credits: 0 },
541
+ meta: {
542
+ success: false,
543
+ message: error.message,
544
+ toolName
545
+ },
546
+ error: {
547
+ code: "RATE_LIMITED",
548
+ message: error.message,
549
+ category: "external"
550
+ },
551
+ retry: {
552
+ allowed: true,
553
+ afterMs: error.retryAfterMs
554
+ }
555
+ };
556
+ }
523
557
  if (error instanceof AppAuthInvalidError) {
524
558
  return {
525
559
  output: null,
@@ -883,7 +917,8 @@ function serializeConfig(config) {
883
917
  description: tool.description,
884
918
  // Read timeout/retries from top-level first, then fallback to config
885
919
  timeout: tool.timeout ?? tool.config?.timeout,
886
- retries: tool.retries ?? tool.config?.retries
920
+ retries: tool.retries ?? tool.config?.retries,
921
+ queueTouchPoints: tool.queueTouchPoints ?? tool.config?.queueTouchPoints
887
922
  })) : [],
888
923
  webhooks: webhookRegistry ? Object.values(webhookRegistry).map((w) => ({
889
924
  name: w.name,
@@ -73,6 +73,7 @@ export declare const TriggerContextSchema: z.ZodObject<{
73
73
  "thread.workflow.completed": "thread.workflow.completed";
74
74
  "thread.follow_up.due": "thread.follow_up.due";
75
75
  "thread.reminder.due": "thread.reminder.due";
76
+ "thread.signal.created": "thread.signal.created";
76
77
  }>, z.ZodString]>;
77
78
  payload: z.ZodRecord<z.ZodString, z.ZodUnknown>;
78
79
  participant: z.ZodOptional<z.ZodObject<{
@@ -199,6 +200,7 @@ export declare const ResolvedTriggerSchema: z.ZodObject<{
199
200
  "thread.workflow.completed": "thread.workflow.completed";
200
201
  "thread.follow_up.due": "thread.follow_up.due";
201
202
  "thread.reminder.due": "thread.reminder.due";
203
+ "thread.signal.created": "thread.signal.created";
202
204
  }>, z.ZodString]>>>;
203
205
  condition: z.ZodOptional<z.ZodString>;
204
206
  }, z.core.$strip>>;
@@ -220,6 +222,7 @@ export declare const ResolvedTriggerSchema: z.ZodObject<{
220
222
  "thread.workflow.completed": "thread.workflow.completed";
221
223
  "thread.follow_up.due": "thread.follow_up.due";
222
224
  "thread.reminder.due": "thread.reminder.due";
225
+ "thread.signal.created": "thread.signal.created";
223
226
  }>, z.ZodString]>;
224
227
  payload: z.ZodRecord<z.ZodString, z.ZodUnknown>;
225
228
  participant: z.ZodOptional<z.ZodObject<{
@@ -9,7 +9,7 @@ export type { InvocationType, ServerHookHandle, InvocationContext, } from './inv
9
9
  export { createToolCallContext, createServerHookContext, createWebhookContext, createWorkflowStepContext, } from './invocation';
10
10
  export type { ToolTrigger, ProvisionToolContext, DeveloperPageActionToolContext, DeveloperFormSubmitToolContext, FieldChangeToolContext, PageActionToolContext, FormSubmitToolContext, AgentToolContext, WorkflowToolContext, CronToolContext, CronContext, ToolExecutionContext, } from './tool-context';
11
11
  export { isProvisionContext, isRuntimeContext, isCronContext, isDeveloperContext } from './tool-context';
12
- export type { ToolResult, ToolSuccess, ToolFailure, ErrorCode, ErrorCategory, ToolWarning, ToolPagination, ToolBilling, ToolRetry, ToolTiming, ToolCompletionHints, ToolConfig, BillingInfo, ToolResponseMeta, ToolEffect, ToolError, ToolExecutionResult, ToolSchemaWithJson, ToolSchema, ToolHandler, ToolDefinition, ToolRegistryEntry, ToolRegistry, ToolName, ToolMetadata, ToolCallResponse, ExecutionScope, } from './tool';
12
+ export type { ToolResult, ToolSuccess, ToolFailure, ErrorCode, ErrorCategory, ToolWarning, ToolPagination, ToolBilling, ToolRetry, ToolTiming, ToolCompletionHints, ToolConfig, QueueTouchPoint, BillingInfo, ToolResponseMeta, ToolEffect, ToolError, ToolExecutionResult, ToolSchemaWithJson, ToolSchema, ToolHandler, ToolDefinition, ToolRegistryEntry, ToolRegistry, ToolName, ToolMetadata, ToolCallResponse, ExecutionScope, } from './tool';
13
13
  export { ToolResponseMetaSchema } from './tool';
14
14
  export { createSuccessResponse, createListResponse, createErrorResponse, createValidationError, createNotFoundError, createAuthError, createRateLimitError, createExternalError, createTimeoutError, createPermissionError, createConflictError, isSuccess, isFailure, isRetryable, getRetryDelay, } from './tool-response';
15
15
  export type { HealthStatus, ComputeLayer, DedicatedServerInstance, ServerlessServerInstance, SkedyulServerInstance, } from './server';
@@ -92,9 +92,18 @@ export interface ToolCompletionHints {
92
92
  idempotent?: boolean;
93
93
  }
94
94
  /**
95
- * Configuration options for tool execution behavior.
96
- * Groups timeout, retry, and completion hint settings.
95
+ * Declares which rate-limit queues a tool may touch (for orchestration probes).
97
96
  */
97
+ export interface QueueTouchPoint {
98
+ /** Queue name from skedyul.config queues (e.g. petbooqz_api) */
99
+ queue: string;
100
+ /** Conservative max upstream HTTP calls this handler may make */
101
+ estimatedCalls: number;
102
+ /** Arg field used as sub-key for per-resource mutex queues */
103
+ subKeyFromArg?: string;
104
+ /** True for correctness mutexes — excluded from rate-limit probe admission */
105
+ mutexOnly?: boolean;
106
+ }
98
107
  export interface ToolConfig {
99
108
  /** Timeout in milliseconds. Defaults to 10000 (10 seconds) if not specified. */
100
109
  timeout?: number;
@@ -102,6 +111,8 @@ export interface ToolConfig {
102
111
  retries?: number;
103
112
  /** Hints for controlling tool completion behavior in agent loops */
104
113
  completionHints?: ToolCompletionHints;
114
+ /** Rate-limit queue touch points for orchestration admission probes */
115
+ queueTouchPoints?: QueueTouchPoint[];
105
116
  }
106
117
  /**
107
118
  * Successful tool execution result.
@@ -179,6 +190,8 @@ export interface ToolExecutionResult<Output = unknown> {
179
190
  meta?: ToolResponseMeta;
180
191
  effect?: ToolEffect;
181
192
  error?: ToolError | null;
193
+ /** Retry guidance for transient failures (e.g. rate limiting) */
194
+ retry?: ToolRetry;
182
195
  /** Rich data blocks for UI rendering (profiles, spreadsheets, datetime cards) */
183
196
  dataBlocks?: import('./data-blocks').DataBlock[];
184
197
  /** Cursor state for cron subscriptions - saved and passed to the next run */
@@ -215,6 +228,8 @@ export interface ToolDefinition<Input = unknown, Output = unknown, InputSchema e
215
228
  outputSchema?: ToolSchema<OutputSchema>;
216
229
  /** Tool execution configuration (timeout, retries, completion hints) */
217
230
  config?: ToolConfig;
231
+ /** Rate-limit queue touch points (also accepted at top level for ergonomics) */
232
+ queueTouchPoints?: QueueTouchPoint[];
218
233
  /**
219
234
  * Execution scope for this tool.
220
235
  * - `installation` (default): Requires appInstallationId, receives sk_wkp_ token.
@@ -233,6 +248,8 @@ export interface ToolRegistryEntry {
233
248
  outputSchema?: ToolSchema;
234
249
  /** Tool execution configuration (timeout, retries, completion hints) */
235
250
  config?: ToolConfig;
251
+ /** Rate-limit queue touch points */
252
+ queueTouchPoints?: QueueTouchPoint[];
236
253
  /** Execution scope for this tool */
237
254
  executionScope?: ExecutionScope;
238
255
  [key: string]: unknown;
@@ -251,6 +268,8 @@ export interface ToolMetadata {
251
268
  retries?: number;
252
269
  /** Tool execution configuration (timeout, retries, completion hints) */
253
270
  config?: ToolConfig;
271
+ /** Rate-limit queue touch points for orchestration admission probes */
272
+ queueTouchPoints?: QueueTouchPoint[];
254
273
  /** Execution scope for this tool */
255
274
  executionScope?: ExecutionScope;
256
275
  }