skedyul 1.7.7 → 1.7.9

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.
@@ -41,6 +41,17 @@ function createWorkflowStepContext(params) {
41
41
  workflowStepId: params.workflowStepId
42
42
  };
43
43
  }
44
+ function createBatchOperationContext(params) {
45
+ return {
46
+ invocationId: params.invocationId,
47
+ invocationType: "batch_operation",
48
+ batchOperationHandle: params.batchOperationHandle,
49
+ batchOperationMethod: params.batchOperationMethod,
50
+ appInstallationId: params.appInstallationId,
51
+ workflowId: params.workflowId,
52
+ workflowRunId: params.workflowRunId
53
+ };
54
+ }
44
55
 
45
56
  // src/types/tool-context.ts
46
57
  function isProvisionContext(ctx) {
@@ -5464,6 +5475,39 @@ async function handleMcpBatchRoute(req, ctx) {
5464
5475
  }
5465
5476
  };
5466
5477
  }
5478
+ function isBatchOperationFailure(result) {
5479
+ return result != null && typeof result === "object" && result.success === false && typeof result.error === "object" && result.error != null;
5480
+ }
5481
+ function toStringEnv(env) {
5482
+ const out = {};
5483
+ for (const [key, value] of Object.entries(env)) {
5484
+ if (value !== void 0) {
5485
+ out[key] = value;
5486
+ }
5487
+ }
5488
+ return out;
5489
+ }
5490
+ function stripBillingFields(result) {
5491
+ const {
5492
+ billing: _billing,
5493
+ success: _success,
5494
+ error: _error,
5495
+ retry: _retry,
5496
+ ...domain
5497
+ } = result;
5498
+ return domain;
5499
+ }
5500
+ function softFailureResponse(error, retry, billing) {
5501
+ return {
5502
+ status: 200,
5503
+ body: {
5504
+ success: false,
5505
+ error,
5506
+ ...retry ? { retry } : {},
5507
+ billing: normalizeBilling(billing)
5508
+ }
5509
+ };
5510
+ }
5467
5511
  async function handleBatchOperationRoute(req, ctx) {
5468
5512
  const bodyResult = parseJsonBody(req);
5469
5513
  if (!bodyResult.success) {
@@ -5472,34 +5516,80 @@ async function handleBatchOperationRoute(req, ctx) {
5472
5516
  const body = bodyResult.data;
5473
5517
  const registry = ctx.batchOperationRegistry;
5474
5518
  if (!registry) {
5475
- return {
5476
- status: 404,
5477
- body: {
5478
- success: false,
5479
- error: "No batch operations registered"
5480
- }
5481
- };
5519
+ return softFailureResponse({
5520
+ code: "NOT_FOUND",
5521
+ message: "No batch operations registered",
5522
+ category: "validation"
5523
+ });
5482
5524
  }
5483
5525
  const operation = registry[body.handle];
5484
5526
  if (!operation) {
5485
- return {
5486
- status: 404,
5487
- body: {
5488
- success: false,
5489
- error: `Batch operation not found: ${body.handle}`
5490
- }
5491
- };
5527
+ return softFailureResponse({
5528
+ code: "NOT_FOUND",
5529
+ message: `Batch operation not found: ${body.handle}`,
5530
+ category: "validation"
5531
+ });
5492
5532
  }
5493
- const log = {
5494
- info: (message, meta) => {
5495
- console.log(`[batch:${body.handle}] ${message}`, meta ?? "");
5496
- },
5497
- warn: (message, meta) => {
5498
- console.warn(`[batch:${body.handle}] ${message}`, meta ?? "");
5499
- },
5500
- error: (message, meta) => {
5501
- console.error(`[batch:${body.handle}] ${message}`, meta ?? "");
5502
- }
5533
+ const context = body.context;
5534
+ if (!context?.workplaceId || !context?.appInstallationId || !context?.appId) {
5535
+ return softFailureResponse({
5536
+ code: "VALIDATION_ERROR",
5537
+ message: "Missing context (workplaceId, appInstallationId, and appId required)",
5538
+ category: "validation"
5539
+ });
5540
+ }
5541
+ if (body.method !== "setup" && body.method !== "iterate") {
5542
+ return softFailureResponse({
5543
+ code: "VALIDATION_ERROR",
5544
+ message: `Unknown method: ${body.method}`,
5545
+ category: "validation"
5546
+ });
5547
+ }
5548
+ const requestEnv = body.env ?? {};
5549
+ const mergedEnv = toStringEnv(buildToolExecutionEnv(requestEnv));
5550
+ const originalEnv = { ...process.env };
5551
+ Object.assign(process.env, mergedEnv);
5552
+ const log = createContextLogger();
5553
+ const app = context.app ?? {
5554
+ id: context.appId,
5555
+ versionId: context.appId
5556
+ };
5557
+ const workplace2 = context.workplace ?? {
5558
+ id: context.workplaceId,
5559
+ subdomain: ""
5560
+ };
5561
+ const invocation = body.invocation ?? {
5562
+ invocationId: `${body.handle}-${body.method}-${Date.now()}`,
5563
+ invocationType: "batch_operation",
5564
+ batchOperationHandle: body.handle,
5565
+ batchOperationMethod: body.method,
5566
+ appInstallationId: context.appInstallationId
5567
+ };
5568
+ const requestConfig = {
5569
+ baseUrl: mergedEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
5570
+ apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
5571
+ };
5572
+ const rateLimitContext = {
5573
+ app,
5574
+ appInstallationId: context.appInstallationId,
5575
+ invocation,
5576
+ preAcquiredLeases: parsePreAcquiredLeases(
5577
+ mergedEnv.SKEDYUL_RATE_LIMIT_LEASES
5578
+ ),
5579
+ heldMutexQueueKeys: parseHeldMutexQueueKeys(
5580
+ mergedEnv.SKEDYUL_HELD_MUTEX_QUEUE_KEYS
5581
+ )
5582
+ };
5583
+ const opContextBase = {
5584
+ workplaceId: context.workplaceId,
5585
+ appInstallationId: context.appInstallationId,
5586
+ appId: context.appId,
5587
+ app,
5588
+ workplace: workplace2,
5589
+ input: body.input,
5590
+ env: mergedEnv,
5591
+ invocation,
5592
+ log
5503
5593
  };
5504
5594
  try {
5505
5595
  if (body.method === "setup") {
@@ -5508,61 +5598,88 @@ async function handleBatchOperationRoute(req, ctx) {
5508
5598
  status: 200,
5509
5599
  body: {
5510
5600
  success: true,
5511
- result: { state: {}, total: void 0 }
5601
+ result: { state: {}, total: void 0 },
5602
+ billing: normalizeBilling(void 0)
5512
5603
  }
5513
5604
  };
5514
5605
  }
5515
- const result = await operation.setup({
5516
- workplaceId: body.workplaceId,
5517
- appInstallationId: body.appInstallationId,
5518
- appId: body.appId,
5519
- input: body.input,
5520
- log
5606
+ const raw2 = await runWithConfig(requestConfig, async () => {
5607
+ return runWithRateLimitExecutionContext(rateLimitContext, async () => {
5608
+ return runWithLogContext({ invocation }, async () => {
5609
+ return operation.setup(opContextBase);
5610
+ });
5611
+ });
5521
5612
  });
5613
+ if (isBatchOperationFailure(raw2)) {
5614
+ return softFailureResponse(raw2.error, raw2.retry, raw2.billing);
5615
+ }
5616
+ const setupResult = raw2;
5617
+ const domain2 = stripBillingFields(
5618
+ setupResult
5619
+ );
5522
5620
  return {
5523
5621
  status: 200,
5524
5622
  body: {
5525
5623
  success: true,
5526
- result
5624
+ result: domain2,
5625
+ billing: normalizeBilling(setupResult.billing)
5527
5626
  }
5528
5627
  };
5529
5628
  }
5530
- if (body.method === "iterate") {
5531
- const result = await operation.iterate({
5532
- workplaceId: body.workplaceId,
5533
- appInstallationId: body.appInstallationId,
5534
- appId: body.appId,
5535
- input: body.input,
5536
- state: body.state,
5537
- page: body.page,
5538
- cursor: body.cursor,
5539
- limit: body.limit,
5540
- log
5629
+ const raw = await runWithConfig(requestConfig, async () => {
5630
+ return runWithRateLimitExecutionContext(rateLimitContext, async () => {
5631
+ return runWithLogContext({ invocation }, async () => {
5632
+ return operation.iterate({
5633
+ ...opContextBase,
5634
+ state: body.state,
5635
+ page: body.page,
5636
+ cursor: body.cursor,
5637
+ limit: body.limit
5638
+ });
5639
+ });
5541
5640
  });
5542
- return {
5543
- status: 200,
5544
- body: {
5545
- success: true,
5546
- result
5547
- }
5548
- };
5641
+ });
5642
+ if (isBatchOperationFailure(raw)) {
5643
+ return softFailureResponse(raw.error, raw.retry, raw.billing);
5549
5644
  }
5645
+ const iterateResult = raw;
5646
+ const domain = stripBillingFields(
5647
+ iterateResult
5648
+ );
5550
5649
  return {
5551
- status: 400,
5650
+ status: 200,
5552
5651
  body: {
5553
- success: false,
5554
- error: `Unknown method: ${body.method}`
5652
+ success: true,
5653
+ result: domain,
5654
+ billing: normalizeBilling(iterateResult.billing)
5555
5655
  }
5556
5656
  };
5557
5657
  } catch (err) {
5558
- log.error("Batch operation failed", { error: err });
5658
+ if (err instanceof RateLimitExceededError) {
5659
+ return softFailureResponse(
5660
+ {
5661
+ code: "RATE_LIMITED",
5662
+ message: err.message,
5663
+ category: "external"
5664
+ },
5665
+ { allowed: true, afterMs: err.retryAfterMs }
5666
+ );
5667
+ }
5668
+ log.error("Batch operation failed", err);
5559
5669
  return {
5560
5670
  status: 500,
5561
5671
  body: {
5562
5672
  success: false,
5563
- error: err instanceof Error ? err.message : String(err)
5673
+ error: {
5674
+ code: "INTERNAL_ERROR",
5675
+ message: err instanceof Error ? err.message : String(err),
5676
+ category: "internal"
5677
+ },
5678
+ billing: normalizeBilling(void 0)
5564
5679
  }
5565
5680
  };
5681
+ } finally {
5682
+ process.env = originalEnv;
5566
5683
  }
5567
5684
  }
5568
5685
  function createNotFoundResponse() {
@@ -8792,6 +8909,7 @@ export {
8792
8909
  computeSkewedExpectedMinorUnits,
8793
8910
  configure,
8794
8911
  createAuthError,
8912
+ createBatchOperationContext,
8795
8913
  createConflictError,
8796
8914
  createContextLogger,
8797
8915
  createErrorResponse,
package/dist/index.js CHANGED
@@ -273,6 +273,7 @@ __export(index_exports, {
273
273
  computeSkewedExpectedMinorUnits: () => computeSkewedExpectedMinorUnits,
274
274
  configure: () => configure,
275
275
  createAuthError: () => createAuthError,
276
+ createBatchOperationContext: () => createBatchOperationContext,
276
277
  createConflictError: () => createConflictError,
277
278
  createContextLogger: () => createContextLogger,
278
279
  createErrorResponse: () => createErrorResponse,
@@ -420,6 +421,17 @@ function createWorkflowStepContext(params) {
420
421
  workflowStepId: params.workflowStepId
421
422
  };
422
423
  }
424
+ function createBatchOperationContext(params) {
425
+ return {
426
+ invocationId: params.invocationId,
427
+ invocationType: "batch_operation",
428
+ batchOperationHandle: params.batchOperationHandle,
429
+ batchOperationMethod: params.batchOperationMethod,
430
+ appInstallationId: params.appInstallationId,
431
+ workflowId: params.workflowId,
432
+ workflowRunId: params.workflowRunId
433
+ };
434
+ }
423
435
 
424
436
  // src/types/tool-context.ts
425
437
  function isProvisionContext(ctx) {
@@ -5843,6 +5855,39 @@ async function handleMcpBatchRoute(req, ctx) {
5843
5855
  }
5844
5856
  };
5845
5857
  }
5858
+ function isBatchOperationFailure(result) {
5859
+ return result != null && typeof result === "object" && result.success === false && typeof result.error === "object" && result.error != null;
5860
+ }
5861
+ function toStringEnv(env) {
5862
+ const out = {};
5863
+ for (const [key, value] of Object.entries(env)) {
5864
+ if (value !== void 0) {
5865
+ out[key] = value;
5866
+ }
5867
+ }
5868
+ return out;
5869
+ }
5870
+ function stripBillingFields(result) {
5871
+ const {
5872
+ billing: _billing,
5873
+ success: _success,
5874
+ error: _error,
5875
+ retry: _retry,
5876
+ ...domain
5877
+ } = result;
5878
+ return domain;
5879
+ }
5880
+ function softFailureResponse(error, retry, billing) {
5881
+ return {
5882
+ status: 200,
5883
+ body: {
5884
+ success: false,
5885
+ error,
5886
+ ...retry ? { retry } : {},
5887
+ billing: normalizeBilling(billing)
5888
+ }
5889
+ };
5890
+ }
5846
5891
  async function handleBatchOperationRoute(req, ctx) {
5847
5892
  const bodyResult = parseJsonBody(req);
5848
5893
  if (!bodyResult.success) {
@@ -5851,34 +5896,80 @@ async function handleBatchOperationRoute(req, ctx) {
5851
5896
  const body = bodyResult.data;
5852
5897
  const registry = ctx.batchOperationRegistry;
5853
5898
  if (!registry) {
5854
- return {
5855
- status: 404,
5856
- body: {
5857
- success: false,
5858
- error: "No batch operations registered"
5859
- }
5860
- };
5899
+ return softFailureResponse({
5900
+ code: "NOT_FOUND",
5901
+ message: "No batch operations registered",
5902
+ category: "validation"
5903
+ });
5861
5904
  }
5862
5905
  const operation = registry[body.handle];
5863
5906
  if (!operation) {
5864
- return {
5865
- status: 404,
5866
- body: {
5867
- success: false,
5868
- error: `Batch operation not found: ${body.handle}`
5869
- }
5870
- };
5907
+ return softFailureResponse({
5908
+ code: "NOT_FOUND",
5909
+ message: `Batch operation not found: ${body.handle}`,
5910
+ category: "validation"
5911
+ });
5871
5912
  }
5872
- const log = {
5873
- info: (message, meta) => {
5874
- console.log(`[batch:${body.handle}] ${message}`, meta ?? "");
5875
- },
5876
- warn: (message, meta) => {
5877
- console.warn(`[batch:${body.handle}] ${message}`, meta ?? "");
5878
- },
5879
- error: (message, meta) => {
5880
- console.error(`[batch:${body.handle}] ${message}`, meta ?? "");
5881
- }
5913
+ const context = body.context;
5914
+ if (!context?.workplaceId || !context?.appInstallationId || !context?.appId) {
5915
+ return softFailureResponse({
5916
+ code: "VALIDATION_ERROR",
5917
+ message: "Missing context (workplaceId, appInstallationId, and appId required)",
5918
+ category: "validation"
5919
+ });
5920
+ }
5921
+ if (body.method !== "setup" && body.method !== "iterate") {
5922
+ return softFailureResponse({
5923
+ code: "VALIDATION_ERROR",
5924
+ message: `Unknown method: ${body.method}`,
5925
+ category: "validation"
5926
+ });
5927
+ }
5928
+ const requestEnv = body.env ?? {};
5929
+ const mergedEnv = toStringEnv(buildToolExecutionEnv(requestEnv));
5930
+ const originalEnv = { ...process.env };
5931
+ Object.assign(process.env, mergedEnv);
5932
+ const log = createContextLogger();
5933
+ const app = context.app ?? {
5934
+ id: context.appId,
5935
+ versionId: context.appId
5936
+ };
5937
+ const workplace2 = context.workplace ?? {
5938
+ id: context.workplaceId,
5939
+ subdomain: ""
5940
+ };
5941
+ const invocation = body.invocation ?? {
5942
+ invocationId: `${body.handle}-${body.method}-${Date.now()}`,
5943
+ invocationType: "batch_operation",
5944
+ batchOperationHandle: body.handle,
5945
+ batchOperationMethod: body.method,
5946
+ appInstallationId: context.appInstallationId
5947
+ };
5948
+ const requestConfig = {
5949
+ baseUrl: mergedEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
5950
+ apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
5951
+ };
5952
+ const rateLimitContext = {
5953
+ app,
5954
+ appInstallationId: context.appInstallationId,
5955
+ invocation,
5956
+ preAcquiredLeases: parsePreAcquiredLeases(
5957
+ mergedEnv.SKEDYUL_RATE_LIMIT_LEASES
5958
+ ),
5959
+ heldMutexQueueKeys: parseHeldMutexQueueKeys(
5960
+ mergedEnv.SKEDYUL_HELD_MUTEX_QUEUE_KEYS
5961
+ )
5962
+ };
5963
+ const opContextBase = {
5964
+ workplaceId: context.workplaceId,
5965
+ appInstallationId: context.appInstallationId,
5966
+ appId: context.appId,
5967
+ app,
5968
+ workplace: workplace2,
5969
+ input: body.input,
5970
+ env: mergedEnv,
5971
+ invocation,
5972
+ log
5882
5973
  };
5883
5974
  try {
5884
5975
  if (body.method === "setup") {
@@ -5887,61 +5978,88 @@ async function handleBatchOperationRoute(req, ctx) {
5887
5978
  status: 200,
5888
5979
  body: {
5889
5980
  success: true,
5890
- result: { state: {}, total: void 0 }
5981
+ result: { state: {}, total: void 0 },
5982
+ billing: normalizeBilling(void 0)
5891
5983
  }
5892
5984
  };
5893
5985
  }
5894
- const result = await operation.setup({
5895
- workplaceId: body.workplaceId,
5896
- appInstallationId: body.appInstallationId,
5897
- appId: body.appId,
5898
- input: body.input,
5899
- log
5986
+ const raw2 = await runWithConfig(requestConfig, async () => {
5987
+ return runWithRateLimitExecutionContext(rateLimitContext, async () => {
5988
+ return runWithLogContext({ invocation }, async () => {
5989
+ return operation.setup(opContextBase);
5990
+ });
5991
+ });
5900
5992
  });
5993
+ if (isBatchOperationFailure(raw2)) {
5994
+ return softFailureResponse(raw2.error, raw2.retry, raw2.billing);
5995
+ }
5996
+ const setupResult = raw2;
5997
+ const domain2 = stripBillingFields(
5998
+ setupResult
5999
+ );
5901
6000
  return {
5902
6001
  status: 200,
5903
6002
  body: {
5904
6003
  success: true,
5905
- result
6004
+ result: domain2,
6005
+ billing: normalizeBilling(setupResult.billing)
5906
6006
  }
5907
6007
  };
5908
6008
  }
5909
- if (body.method === "iterate") {
5910
- const result = await operation.iterate({
5911
- workplaceId: body.workplaceId,
5912
- appInstallationId: body.appInstallationId,
5913
- appId: body.appId,
5914
- input: body.input,
5915
- state: body.state,
5916
- page: body.page,
5917
- cursor: body.cursor,
5918
- limit: body.limit,
5919
- log
6009
+ const raw = await runWithConfig(requestConfig, async () => {
6010
+ return runWithRateLimitExecutionContext(rateLimitContext, async () => {
6011
+ return runWithLogContext({ invocation }, async () => {
6012
+ return operation.iterate({
6013
+ ...opContextBase,
6014
+ state: body.state,
6015
+ page: body.page,
6016
+ cursor: body.cursor,
6017
+ limit: body.limit
6018
+ });
6019
+ });
5920
6020
  });
5921
- return {
5922
- status: 200,
5923
- body: {
5924
- success: true,
5925
- result
5926
- }
5927
- };
6021
+ });
6022
+ if (isBatchOperationFailure(raw)) {
6023
+ return softFailureResponse(raw.error, raw.retry, raw.billing);
5928
6024
  }
6025
+ const iterateResult = raw;
6026
+ const domain = stripBillingFields(
6027
+ iterateResult
6028
+ );
5929
6029
  return {
5930
- status: 400,
6030
+ status: 200,
5931
6031
  body: {
5932
- success: false,
5933
- error: `Unknown method: ${body.method}`
6032
+ success: true,
6033
+ result: domain,
6034
+ billing: normalizeBilling(iterateResult.billing)
5934
6035
  }
5935
6036
  };
5936
6037
  } catch (err) {
5937
- log.error("Batch operation failed", { error: err });
6038
+ if (err instanceof RateLimitExceededError) {
6039
+ return softFailureResponse(
6040
+ {
6041
+ code: "RATE_LIMITED",
6042
+ message: err.message,
6043
+ category: "external"
6044
+ },
6045
+ { allowed: true, afterMs: err.retryAfterMs }
6046
+ );
6047
+ }
6048
+ log.error("Batch operation failed", err);
5938
6049
  return {
5939
6050
  status: 500,
5940
6051
  body: {
5941
6052
  success: false,
5942
- error: err instanceof Error ? err.message : String(err)
6053
+ error: {
6054
+ code: "INTERNAL_ERROR",
6055
+ message: err instanceof Error ? err.message : String(err),
6056
+ category: "internal"
6057
+ },
6058
+ billing: normalizeBilling(void 0)
5943
6059
  }
5944
6060
  };
6061
+ } finally {
6062
+ process.env = originalEnv;
5945
6063
  }
5946
6064
  }
5947
6065
  function createNotFoundResponse() {
@@ -9172,6 +9290,7 @@ var index_default = { z: import_v414.z };
9172
9290
  computeSkewedExpectedMinorUnits,
9173
9291
  configure,
9174
9292
  createAuthError,
9293
+ createBatchOperationContext,
9175
9294
  createConflictError,
9176
9295
  createContextLogger,
9177
9296
  createErrorResponse,
@@ -62,6 +62,9 @@ export declare function handleMcpRoute(req: UnifiedRequest, ctx: RouteContext):
62
62
  export declare function handleMcpBatchRoute(req: UnifiedRequest, ctx: RouteContext): Promise<UnifiedResponse>;
63
63
  /**
64
64
  * Handle /batch-operation route for invoking batch operation setup/iterate.
65
+ *
66
+ * Mirrors tool handlers: buildToolExecutionEnv, process.env assign/restore,
67
+ * runWithConfig + rate-limit + log ALS wrappers, tool-shaped billing/error envelope.
65
68
  */
66
69
  export declare function handleBatchOperationRoute(req: UnifiedRequest, ctx: RouteContext): Promise<UnifiedResponse>;
67
70
  /**