skedyul 1.7.8 → 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,49 +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
5533
  const context = body.context;
5494
5534
  if (!context?.workplaceId || !context?.appInstallationId || !context?.appId) {
5495
- return {
5496
- status: 400,
5497
- body: {
5498
- success: false,
5499
- error: "Missing context (workplaceId, appInstallationId, and appId required)"
5500
- }
5501
- };
5535
+ return softFailureResponse({
5536
+ code: "VALIDATION_ERROR",
5537
+ message: "Missing context (workplaceId, appInstallationId, and appId required)",
5538
+ category: "validation"
5539
+ });
5502
5540
  }
5503
- const env = body.env ?? {};
5504
- const log = {
5505
- info: (message, meta) => {
5506
- console.log(`[batch:${body.handle}] ${message}`, meta ?? "");
5507
- },
5508
- warn: (message, meta) => {
5509
- console.warn(`[batch:${body.handle}] ${message}`, meta ?? "");
5510
- },
5511
- error: (message, meta) => {
5512
- console.error(`[batch:${body.handle}] ${message}`, meta ?? "");
5513
- }
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
5514
5567
  };
5515
5568
  const requestConfig = {
5516
- baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
5517
- apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
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
5518
5593
  };
5519
5594
  try {
5520
5595
  if (body.method === "setup") {
@@ -5523,67 +5598,88 @@ async function handleBatchOperationRoute(req, ctx) {
5523
5598
  status: 200,
5524
5599
  body: {
5525
5600
  success: true,
5526
- result: { state: {}, total: void 0 }
5601
+ result: { state: {}, total: void 0 },
5602
+ billing: normalizeBilling(void 0)
5527
5603
  }
5528
5604
  };
5529
5605
  }
5530
- const result = await runWithConfig(requestConfig, async () => {
5531
- return operation.setup({
5532
- workplaceId: context.workplaceId,
5533
- appInstallationId: context.appInstallationId,
5534
- appId: context.appId,
5535
- input: body.input,
5536
- env,
5537
- log
5606
+ const raw2 = await runWithConfig(requestConfig, async () => {
5607
+ return runWithRateLimitExecutionContext(rateLimitContext, async () => {
5608
+ return runWithLogContext({ invocation }, async () => {
5609
+ return operation.setup(opContextBase);
5610
+ });
5538
5611
  });
5539
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
+ );
5540
5620
  return {
5541
5621
  status: 200,
5542
5622
  body: {
5543
5623
  success: true,
5544
- result
5624
+ result: domain2,
5625
+ billing: normalizeBilling(setupResult.billing)
5545
5626
  }
5546
5627
  };
5547
5628
  }
5548
- if (body.method === "iterate") {
5549
- const result = await runWithConfig(requestConfig, async () => {
5550
- return operation.iterate({
5551
- workplaceId: context.workplaceId,
5552
- appInstallationId: context.appInstallationId,
5553
- appId: context.appId,
5554
- input: body.input,
5555
- state: body.state,
5556
- page: body.page,
5557
- cursor: body.cursor,
5558
- limit: body.limit,
5559
- env,
5560
- 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
+ });
5561
5639
  });
5562
5640
  });
5563
- return {
5564
- status: 200,
5565
- body: {
5566
- success: true,
5567
- result
5568
- }
5569
- };
5641
+ });
5642
+ if (isBatchOperationFailure(raw)) {
5643
+ return softFailureResponse(raw.error, raw.retry, raw.billing);
5570
5644
  }
5645
+ const iterateResult = raw;
5646
+ const domain = stripBillingFields(
5647
+ iterateResult
5648
+ );
5571
5649
  return {
5572
- status: 400,
5650
+ status: 200,
5573
5651
  body: {
5574
- success: false,
5575
- error: `Unknown method: ${body.method}`
5652
+ success: true,
5653
+ result: domain,
5654
+ billing: normalizeBilling(iterateResult.billing)
5576
5655
  }
5577
5656
  };
5578
5657
  } catch (err) {
5579
- 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);
5580
5669
  return {
5581
5670
  status: 500,
5582
5671
  body: {
5583
5672
  success: false,
5584
- 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)
5585
5679
  }
5586
5680
  };
5681
+ } finally {
5682
+ process.env = originalEnv;
5587
5683
  }
5588
5684
  }
5589
5685
  function createNotFoundResponse() {
@@ -8813,6 +8909,7 @@ export {
8813
8909
  computeSkewedExpectedMinorUnits,
8814
8910
  configure,
8815
8911
  createAuthError,
8912
+ createBatchOperationContext,
8816
8913
  createConflictError,
8817
8914
  createContextLogger,
8818
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,49 +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
5913
  const context = body.context;
5873
5914
  if (!context?.workplaceId || !context?.appInstallationId || !context?.appId) {
5874
- return {
5875
- status: 400,
5876
- body: {
5877
- success: false,
5878
- error: "Missing context (workplaceId, appInstallationId, and appId required)"
5879
- }
5880
- };
5915
+ return softFailureResponse({
5916
+ code: "VALIDATION_ERROR",
5917
+ message: "Missing context (workplaceId, appInstallationId, and appId required)",
5918
+ category: "validation"
5919
+ });
5881
5920
  }
5882
- const env = body.env ?? {};
5883
- const log = {
5884
- info: (message, meta) => {
5885
- console.log(`[batch:${body.handle}] ${message}`, meta ?? "");
5886
- },
5887
- warn: (message, meta) => {
5888
- console.warn(`[batch:${body.handle}] ${message}`, meta ?? "");
5889
- },
5890
- error: (message, meta) => {
5891
- console.error(`[batch:${body.handle}] ${message}`, meta ?? "");
5892
- }
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
5893
5947
  };
5894
5948
  const requestConfig = {
5895
- baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
5896
- apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
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
5897
5973
  };
5898
5974
  try {
5899
5975
  if (body.method === "setup") {
@@ -5902,67 +5978,88 @@ async function handleBatchOperationRoute(req, ctx) {
5902
5978
  status: 200,
5903
5979
  body: {
5904
5980
  success: true,
5905
- result: { state: {}, total: void 0 }
5981
+ result: { state: {}, total: void 0 },
5982
+ billing: normalizeBilling(void 0)
5906
5983
  }
5907
5984
  };
5908
5985
  }
5909
- const result = await runWithConfig(requestConfig, async () => {
5910
- return operation.setup({
5911
- workplaceId: context.workplaceId,
5912
- appInstallationId: context.appInstallationId,
5913
- appId: context.appId,
5914
- input: body.input,
5915
- env,
5916
- log
5986
+ const raw2 = await runWithConfig(requestConfig, async () => {
5987
+ return runWithRateLimitExecutionContext(rateLimitContext, async () => {
5988
+ return runWithLogContext({ invocation }, async () => {
5989
+ return operation.setup(opContextBase);
5990
+ });
5917
5991
  });
5918
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
+ );
5919
6000
  return {
5920
6001
  status: 200,
5921
6002
  body: {
5922
6003
  success: true,
5923
- result
6004
+ result: domain2,
6005
+ billing: normalizeBilling(setupResult.billing)
5924
6006
  }
5925
6007
  };
5926
6008
  }
5927
- if (body.method === "iterate") {
5928
- const result = await runWithConfig(requestConfig, async () => {
5929
- return operation.iterate({
5930
- workplaceId: context.workplaceId,
5931
- appInstallationId: context.appInstallationId,
5932
- appId: context.appId,
5933
- input: body.input,
5934
- state: body.state,
5935
- page: body.page,
5936
- cursor: body.cursor,
5937
- limit: body.limit,
5938
- env,
5939
- 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
+ });
5940
6019
  });
5941
6020
  });
5942
- return {
5943
- status: 200,
5944
- body: {
5945
- success: true,
5946
- result
5947
- }
5948
- };
6021
+ });
6022
+ if (isBatchOperationFailure(raw)) {
6023
+ return softFailureResponse(raw.error, raw.retry, raw.billing);
5949
6024
  }
6025
+ const iterateResult = raw;
6026
+ const domain = stripBillingFields(
6027
+ iterateResult
6028
+ );
5950
6029
  return {
5951
- status: 400,
6030
+ status: 200,
5952
6031
  body: {
5953
- success: false,
5954
- error: `Unknown method: ${body.method}`
6032
+ success: true,
6033
+ result: domain,
6034
+ billing: normalizeBilling(iterateResult.billing)
5955
6035
  }
5956
6036
  };
5957
6037
  } catch (err) {
5958
- 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);
5959
6049
  return {
5960
6050
  status: 500,
5961
6051
  body: {
5962
6052
  success: false,
5963
- 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)
5964
6059
  }
5965
6060
  };
6061
+ } finally {
6062
+ process.env = originalEnv;
5966
6063
  }
5967
6064
  }
5968
6065
  function createNotFoundResponse() {
@@ -9193,6 +9290,7 @@ var index_default = { z: import_v414.z };
9193
9290
  computeSkewedExpectedMinorUnits,
9194
9291
  configure,
9195
9292
  createAuthError,
9293
+ createBatchOperationContext,
9196
9294
  createConflictError,
9197
9295
  createContextLogger,
9198
9296
  createErrorResponse,
@@ -63,8 +63,8 @@ export declare function handleMcpBatchRoute(req: UnifiedRequest, ctx: RouteConte
63
63
  /**
64
64
  * Handle /batch-operation route for invoking batch operation setup/iterate.
65
65
  *
66
- * Mirrors tool/install handlers: platform passes env + context in the body;
67
- * we wrap handlers in runWithConfig so SDK clients (instance.*, etc.) work.
66
+ * Mirrors tool handlers: buildToolExecutionEnv, process.env assign/restore,
67
+ * runWithConfig + rate-limit + log ALS wrappers, tool-shaped billing/error envelope.
68
68
  */
69
69
  export declare function handleBatchOperationRoute(req: UnifiedRequest, ctx: RouteContext): Promise<UnifiedResponse>;
70
70
  /**