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.
- package/dist/.build-stamp +1 -1
- package/dist/cli/index.js +154 -69
- package/dist/config/types/batch-operation.d.ts +33 -4
- package/dist/dedicated/server.js +154 -69
- package/dist/esm/index.mjs +166 -69
- package/dist/index.js +167 -69
- package/dist/server/route-handlers/handlers.d.ts +2 -2
- package/dist/server.js +154 -69
- package/dist/serverless/server.mjs +154 -69
- package/dist/types/index.d.ts +1 -1
- package/dist/types/invocation.d.ts +16 -1
- package/package.json +2 -2
package/dist/.build-stamp
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
1785987631012
|
package/dist/cli/index.js
CHANGED
|
@@ -4658,6 +4658,39 @@ async function handleMcpBatchRoute(req, ctx) {
|
|
|
4658
4658
|
}
|
|
4659
4659
|
};
|
|
4660
4660
|
}
|
|
4661
|
+
function isBatchOperationFailure(result) {
|
|
4662
|
+
return result != null && typeof result === "object" && result.success === false && typeof result.error === "object" && result.error != null;
|
|
4663
|
+
}
|
|
4664
|
+
function toStringEnv(env) {
|
|
4665
|
+
const out = {};
|
|
4666
|
+
for (const [key, value] of Object.entries(env)) {
|
|
4667
|
+
if (value !== void 0) {
|
|
4668
|
+
out[key] = value;
|
|
4669
|
+
}
|
|
4670
|
+
}
|
|
4671
|
+
return out;
|
|
4672
|
+
}
|
|
4673
|
+
function stripBillingFields(result) {
|
|
4674
|
+
const {
|
|
4675
|
+
billing: _billing,
|
|
4676
|
+
success: _success,
|
|
4677
|
+
error: _error,
|
|
4678
|
+
retry: _retry,
|
|
4679
|
+
...domain
|
|
4680
|
+
} = result;
|
|
4681
|
+
return domain;
|
|
4682
|
+
}
|
|
4683
|
+
function softFailureResponse(error, retry, billing) {
|
|
4684
|
+
return {
|
|
4685
|
+
status: 200,
|
|
4686
|
+
body: {
|
|
4687
|
+
success: false,
|
|
4688
|
+
error,
|
|
4689
|
+
...retry ? { retry } : {},
|
|
4690
|
+
billing: normalizeBilling(billing)
|
|
4691
|
+
}
|
|
4692
|
+
};
|
|
4693
|
+
}
|
|
4661
4694
|
async function handleBatchOperationRoute(req, ctx) {
|
|
4662
4695
|
const bodyResult = parseJsonBody(req);
|
|
4663
4696
|
if (!bodyResult.success) {
|
|
@@ -4666,49 +4699,80 @@ async function handleBatchOperationRoute(req, ctx) {
|
|
|
4666
4699
|
const body = bodyResult.data;
|
|
4667
4700
|
const registry = ctx.batchOperationRegistry;
|
|
4668
4701
|
if (!registry) {
|
|
4669
|
-
return {
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
}
|
|
4675
|
-
};
|
|
4702
|
+
return softFailureResponse({
|
|
4703
|
+
code: "NOT_FOUND",
|
|
4704
|
+
message: "No batch operations registered",
|
|
4705
|
+
category: "validation"
|
|
4706
|
+
});
|
|
4676
4707
|
}
|
|
4677
4708
|
const operation = registry[body.handle];
|
|
4678
4709
|
if (!operation) {
|
|
4679
|
-
return {
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
}
|
|
4685
|
-
};
|
|
4710
|
+
return softFailureResponse({
|
|
4711
|
+
code: "NOT_FOUND",
|
|
4712
|
+
message: `Batch operation not found: ${body.handle}`,
|
|
4713
|
+
category: "validation"
|
|
4714
|
+
});
|
|
4686
4715
|
}
|
|
4687
4716
|
const context = body.context;
|
|
4688
4717
|
if (!context?.workplaceId || !context?.appInstallationId || !context?.appId) {
|
|
4689
|
-
return {
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
}
|
|
4695
|
-
};
|
|
4718
|
+
return softFailureResponse({
|
|
4719
|
+
code: "VALIDATION_ERROR",
|
|
4720
|
+
message: "Missing context (workplaceId, appInstallationId, and appId required)",
|
|
4721
|
+
category: "validation"
|
|
4722
|
+
});
|
|
4696
4723
|
}
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4724
|
+
if (body.method !== "setup" && body.method !== "iterate") {
|
|
4725
|
+
return softFailureResponse({
|
|
4726
|
+
code: "VALIDATION_ERROR",
|
|
4727
|
+
message: `Unknown method: ${body.method}`,
|
|
4728
|
+
category: "validation"
|
|
4729
|
+
});
|
|
4730
|
+
}
|
|
4731
|
+
const requestEnv = body.env ?? {};
|
|
4732
|
+
const mergedEnv = toStringEnv(buildToolExecutionEnv(requestEnv));
|
|
4733
|
+
const originalEnv = { ...process.env };
|
|
4734
|
+
Object.assign(process.env, mergedEnv);
|
|
4735
|
+
const log = createContextLogger();
|
|
4736
|
+
const app = context.app ?? {
|
|
4737
|
+
id: context.appId,
|
|
4738
|
+
versionId: context.appId
|
|
4739
|
+
};
|
|
4740
|
+
const workplace = context.workplace ?? {
|
|
4741
|
+
id: context.workplaceId,
|
|
4742
|
+
subdomain: ""
|
|
4743
|
+
};
|
|
4744
|
+
const invocation = body.invocation ?? {
|
|
4745
|
+
invocationId: `${body.handle}-${body.method}-${Date.now()}`,
|
|
4746
|
+
invocationType: "batch_operation",
|
|
4747
|
+
batchOperationHandle: body.handle,
|
|
4748
|
+
batchOperationMethod: body.method,
|
|
4749
|
+
appInstallationId: context.appInstallationId
|
|
4708
4750
|
};
|
|
4709
4751
|
const requestConfig = {
|
|
4710
|
-
baseUrl:
|
|
4711
|
-
apiToken:
|
|
4752
|
+
baseUrl: mergedEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
4753
|
+
apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
4754
|
+
};
|
|
4755
|
+
const rateLimitContext = {
|
|
4756
|
+
app,
|
|
4757
|
+
appInstallationId: context.appInstallationId,
|
|
4758
|
+
invocation,
|
|
4759
|
+
preAcquiredLeases: parsePreAcquiredLeases(
|
|
4760
|
+
mergedEnv.SKEDYUL_RATE_LIMIT_LEASES
|
|
4761
|
+
),
|
|
4762
|
+
heldMutexQueueKeys: parseHeldMutexQueueKeys(
|
|
4763
|
+
mergedEnv.SKEDYUL_HELD_MUTEX_QUEUE_KEYS
|
|
4764
|
+
)
|
|
4765
|
+
};
|
|
4766
|
+
const opContextBase = {
|
|
4767
|
+
workplaceId: context.workplaceId,
|
|
4768
|
+
appInstallationId: context.appInstallationId,
|
|
4769
|
+
appId: context.appId,
|
|
4770
|
+
app,
|
|
4771
|
+
workplace,
|
|
4772
|
+
input: body.input,
|
|
4773
|
+
env: mergedEnv,
|
|
4774
|
+
invocation,
|
|
4775
|
+
log
|
|
4712
4776
|
};
|
|
4713
4777
|
try {
|
|
4714
4778
|
if (body.method === "setup") {
|
|
@@ -4717,67 +4781,88 @@ async function handleBatchOperationRoute(req, ctx) {
|
|
|
4717
4781
|
status: 200,
|
|
4718
4782
|
body: {
|
|
4719
4783
|
success: true,
|
|
4720
|
-
result: { state: {}, total: void 0 }
|
|
4784
|
+
result: { state: {}, total: void 0 },
|
|
4785
|
+
billing: normalizeBilling(void 0)
|
|
4721
4786
|
}
|
|
4722
4787
|
};
|
|
4723
4788
|
}
|
|
4724
|
-
const
|
|
4725
|
-
return
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
input: body.input,
|
|
4730
|
-
env,
|
|
4731
|
-
log
|
|
4789
|
+
const raw2 = await runWithConfig(requestConfig, async () => {
|
|
4790
|
+
return runWithRateLimitExecutionContext(rateLimitContext, async () => {
|
|
4791
|
+
return runWithLogContext({ invocation }, async () => {
|
|
4792
|
+
return operation.setup(opContextBase);
|
|
4793
|
+
});
|
|
4732
4794
|
});
|
|
4733
4795
|
});
|
|
4796
|
+
if (isBatchOperationFailure(raw2)) {
|
|
4797
|
+
return softFailureResponse(raw2.error, raw2.retry, raw2.billing);
|
|
4798
|
+
}
|
|
4799
|
+
const setupResult = raw2;
|
|
4800
|
+
const domain2 = stripBillingFields(
|
|
4801
|
+
setupResult
|
|
4802
|
+
);
|
|
4734
4803
|
return {
|
|
4735
4804
|
status: 200,
|
|
4736
4805
|
body: {
|
|
4737
4806
|
success: true,
|
|
4738
|
-
result
|
|
4807
|
+
result: domain2,
|
|
4808
|
+
billing: normalizeBilling(setupResult.billing)
|
|
4739
4809
|
}
|
|
4740
4810
|
};
|
|
4741
4811
|
}
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
return
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
limit: body.limit,
|
|
4753
|
-
env,
|
|
4754
|
-
log
|
|
4812
|
+
const raw = await runWithConfig(requestConfig, async () => {
|
|
4813
|
+
return runWithRateLimitExecutionContext(rateLimitContext, async () => {
|
|
4814
|
+
return runWithLogContext({ invocation }, async () => {
|
|
4815
|
+
return operation.iterate({
|
|
4816
|
+
...opContextBase,
|
|
4817
|
+
state: body.state,
|
|
4818
|
+
page: body.page,
|
|
4819
|
+
cursor: body.cursor,
|
|
4820
|
+
limit: body.limit
|
|
4821
|
+
});
|
|
4755
4822
|
});
|
|
4756
4823
|
});
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
success: true,
|
|
4761
|
-
result
|
|
4762
|
-
}
|
|
4763
|
-
};
|
|
4824
|
+
});
|
|
4825
|
+
if (isBatchOperationFailure(raw)) {
|
|
4826
|
+
return softFailureResponse(raw.error, raw.retry, raw.billing);
|
|
4764
4827
|
}
|
|
4828
|
+
const iterateResult = raw;
|
|
4829
|
+
const domain = stripBillingFields(
|
|
4830
|
+
iterateResult
|
|
4831
|
+
);
|
|
4765
4832
|
return {
|
|
4766
|
-
status:
|
|
4833
|
+
status: 200,
|
|
4767
4834
|
body: {
|
|
4768
|
-
success:
|
|
4769
|
-
|
|
4835
|
+
success: true,
|
|
4836
|
+
result: domain,
|
|
4837
|
+
billing: normalizeBilling(iterateResult.billing)
|
|
4770
4838
|
}
|
|
4771
4839
|
};
|
|
4772
4840
|
} catch (err) {
|
|
4773
|
-
|
|
4841
|
+
if (err instanceof RateLimitExceededError) {
|
|
4842
|
+
return softFailureResponse(
|
|
4843
|
+
{
|
|
4844
|
+
code: "RATE_LIMITED",
|
|
4845
|
+
message: err.message,
|
|
4846
|
+
category: "external"
|
|
4847
|
+
},
|
|
4848
|
+
{ allowed: true, afterMs: err.retryAfterMs }
|
|
4849
|
+
);
|
|
4850
|
+
}
|
|
4851
|
+
log.error("Batch operation failed", err);
|
|
4774
4852
|
return {
|
|
4775
4853
|
status: 500,
|
|
4776
4854
|
body: {
|
|
4777
4855
|
success: false,
|
|
4778
|
-
error:
|
|
4856
|
+
error: {
|
|
4857
|
+
code: "INTERNAL_ERROR",
|
|
4858
|
+
message: err instanceof Error ? err.message : String(err),
|
|
4859
|
+
category: "internal"
|
|
4860
|
+
},
|
|
4861
|
+
billing: normalizeBilling(void 0)
|
|
4779
4862
|
}
|
|
4780
4863
|
};
|
|
4864
|
+
} finally {
|
|
4865
|
+
process.env = originalEnv;
|
|
4781
4866
|
}
|
|
4782
4867
|
}
|
|
4783
4868
|
function createNotFoundResponse() {
|
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
* like member imports, data syncs, and batch API calls. The platform handles
|
|
6
6
|
* orchestration, progress tracking, CRM mapping, and upsert.
|
|
7
7
|
*/
|
|
8
|
+
import type { InvocationContext } from '../../types/invocation';
|
|
9
|
+
import type { ToolBilling, ToolError, ToolRetry } from '../../types/tool';
|
|
10
|
+
import type { AppInfo, WorkplaceInfo } from '../../types/shared';
|
|
11
|
+
export type { ToolBilling, ToolError, ToolRetry };
|
|
8
12
|
/**
|
|
9
13
|
* Pagination response from an iterate function.
|
|
10
14
|
* Supports both cursor-based and page-based pagination.
|
|
@@ -21,6 +25,15 @@ export interface BatchPagination {
|
|
|
21
25
|
/** Items per page */
|
|
22
26
|
limit?: number;
|
|
23
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Soft-failure return from setup/iterate (tool-call shaped).
|
|
30
|
+
*/
|
|
31
|
+
export interface BatchOperationFailure {
|
|
32
|
+
success: false;
|
|
33
|
+
error: ToolError;
|
|
34
|
+
retry?: ToolRetry;
|
|
35
|
+
billing?: ToolBilling;
|
|
36
|
+
}
|
|
24
37
|
/**
|
|
25
38
|
* Result from a batch operation setup function.
|
|
26
39
|
*/
|
|
@@ -29,6 +42,10 @@ export interface BatchOperationSetupResult {
|
|
|
29
42
|
state?: Record<string, unknown>;
|
|
30
43
|
/** Optional total count for progress display */
|
|
31
44
|
total?: number;
|
|
45
|
+
/** Optional billing (normalized by the route if omitted) */
|
|
46
|
+
billing?: ToolBilling;
|
|
47
|
+
/** Optional success discriminator (default true when domain fields present) */
|
|
48
|
+
success?: true;
|
|
32
49
|
}
|
|
33
50
|
/**
|
|
34
51
|
* Result from a batch operation iterate function.
|
|
@@ -40,7 +57,13 @@ export interface BatchOperationIterateResult {
|
|
|
40
57
|
pagination: BatchPagination;
|
|
41
58
|
/** Optional state to persist for the next iterate call */
|
|
42
59
|
state?: Record<string, unknown>;
|
|
60
|
+
/** Optional billing (normalized by the route if omitted) */
|
|
61
|
+
billing?: ToolBilling;
|
|
62
|
+
/** Optional success discriminator (default true when domain fields present) */
|
|
63
|
+
success?: true;
|
|
43
64
|
}
|
|
65
|
+
export type BatchOperationSetupReturn = BatchOperationSetupResult | BatchOperationFailure;
|
|
66
|
+
export type BatchOperationIterateReturn = BatchOperationIterateResult | BatchOperationFailure;
|
|
44
67
|
/**
|
|
45
68
|
* Context passed to batch operation setup and iterate functions.
|
|
46
69
|
*/
|
|
@@ -51,15 +74,21 @@ export interface BatchOperationContext {
|
|
|
51
74
|
appInstallationId: string;
|
|
52
75
|
/** App ID */
|
|
53
76
|
appId: string;
|
|
77
|
+
/** App info with version (for rate-limit / SDK context) */
|
|
78
|
+
app?: AppInfo;
|
|
79
|
+
/** Workplace info when available */
|
|
80
|
+
workplace?: WorkplaceInfo;
|
|
54
81
|
/** User-provided input (if any) */
|
|
55
82
|
input?: Record<string, unknown>;
|
|
56
83
|
/** State from previous setup or iterate call */
|
|
57
84
|
state?: Record<string, unknown>;
|
|
58
85
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
86
|
+
* Merged environment (process.env + baked MCP_ENV + request env),
|
|
87
|
+
* same layering as tool handlers via buildToolExecutionEnv.
|
|
61
88
|
*/
|
|
62
89
|
env: Record<string, string>;
|
|
90
|
+
/** Invocation context for log traceability */
|
|
91
|
+
invocation?: InvocationContext;
|
|
63
92
|
/** Logger instance */
|
|
64
93
|
log: {
|
|
65
94
|
info: (message: string, meta?: Record<string, unknown>) => void;
|
|
@@ -70,7 +99,7 @@ export interface BatchOperationContext {
|
|
|
70
99
|
/**
|
|
71
100
|
* Type for batch operation setup function.
|
|
72
101
|
*/
|
|
73
|
-
export type BatchOperationSetupFn = (ctx: BatchOperationContext) => Promise<
|
|
102
|
+
export type BatchOperationSetupFn = (ctx: BatchOperationContext) => Promise<BatchOperationSetupReturn>;
|
|
74
103
|
/**
|
|
75
104
|
* Type for batch operation iterate function.
|
|
76
105
|
*/
|
|
@@ -81,7 +110,7 @@ export type BatchOperationIterateFn = (ctx: BatchOperationContext & {
|
|
|
81
110
|
cursor?: string | number;
|
|
82
111
|
/** Items per page */
|
|
83
112
|
limit: number;
|
|
84
|
-
}) => Promise<
|
|
113
|
+
}) => Promise<BatchOperationIterateReturn>;
|
|
85
114
|
/**
|
|
86
115
|
* Batch operation definition.
|
|
87
116
|
*
|
package/dist/dedicated/server.js
CHANGED
|
@@ -1988,6 +1988,39 @@ async function handleMcpBatchRoute(req, ctx) {
|
|
|
1988
1988
|
}
|
|
1989
1989
|
};
|
|
1990
1990
|
}
|
|
1991
|
+
function isBatchOperationFailure(result) {
|
|
1992
|
+
return result != null && typeof result === "object" && result.success === false && typeof result.error === "object" && result.error != null;
|
|
1993
|
+
}
|
|
1994
|
+
function toStringEnv(env) {
|
|
1995
|
+
const out = {};
|
|
1996
|
+
for (const [key, value] of Object.entries(env)) {
|
|
1997
|
+
if (value !== void 0) {
|
|
1998
|
+
out[key] = value;
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
return out;
|
|
2002
|
+
}
|
|
2003
|
+
function stripBillingFields(result) {
|
|
2004
|
+
const {
|
|
2005
|
+
billing: _billing,
|
|
2006
|
+
success: _success,
|
|
2007
|
+
error: _error,
|
|
2008
|
+
retry: _retry,
|
|
2009
|
+
...domain
|
|
2010
|
+
} = result;
|
|
2011
|
+
return domain;
|
|
2012
|
+
}
|
|
2013
|
+
function softFailureResponse(error, retry, billing) {
|
|
2014
|
+
return {
|
|
2015
|
+
status: 200,
|
|
2016
|
+
body: {
|
|
2017
|
+
success: false,
|
|
2018
|
+
error,
|
|
2019
|
+
...retry ? { retry } : {},
|
|
2020
|
+
billing: normalizeBilling(billing)
|
|
2021
|
+
}
|
|
2022
|
+
};
|
|
2023
|
+
}
|
|
1991
2024
|
async function handleBatchOperationRoute(req, ctx) {
|
|
1992
2025
|
const bodyResult = parseJsonBody(req);
|
|
1993
2026
|
if (!bodyResult.success) {
|
|
@@ -1996,49 +2029,80 @@ async function handleBatchOperationRoute(req, ctx) {
|
|
|
1996
2029
|
const body = bodyResult.data;
|
|
1997
2030
|
const registry = ctx.batchOperationRegistry;
|
|
1998
2031
|
if (!registry) {
|
|
1999
|
-
return {
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
}
|
|
2005
|
-
};
|
|
2032
|
+
return softFailureResponse({
|
|
2033
|
+
code: "NOT_FOUND",
|
|
2034
|
+
message: "No batch operations registered",
|
|
2035
|
+
category: "validation"
|
|
2036
|
+
});
|
|
2006
2037
|
}
|
|
2007
2038
|
const operation = registry[body.handle];
|
|
2008
2039
|
if (!operation) {
|
|
2009
|
-
return {
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
}
|
|
2015
|
-
};
|
|
2040
|
+
return softFailureResponse({
|
|
2041
|
+
code: "NOT_FOUND",
|
|
2042
|
+
message: `Batch operation not found: ${body.handle}`,
|
|
2043
|
+
category: "validation"
|
|
2044
|
+
});
|
|
2016
2045
|
}
|
|
2017
2046
|
const context = body.context;
|
|
2018
2047
|
if (!context?.workplaceId || !context?.appInstallationId || !context?.appId) {
|
|
2019
|
-
return {
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
}
|
|
2025
|
-
};
|
|
2048
|
+
return softFailureResponse({
|
|
2049
|
+
code: "VALIDATION_ERROR",
|
|
2050
|
+
message: "Missing context (workplaceId, appInstallationId, and appId required)",
|
|
2051
|
+
category: "validation"
|
|
2052
|
+
});
|
|
2026
2053
|
}
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2054
|
+
if (body.method !== "setup" && body.method !== "iterate") {
|
|
2055
|
+
return softFailureResponse({
|
|
2056
|
+
code: "VALIDATION_ERROR",
|
|
2057
|
+
message: `Unknown method: ${body.method}`,
|
|
2058
|
+
category: "validation"
|
|
2059
|
+
});
|
|
2060
|
+
}
|
|
2061
|
+
const requestEnv = body.env ?? {};
|
|
2062
|
+
const mergedEnv = toStringEnv(buildToolExecutionEnv(requestEnv));
|
|
2063
|
+
const originalEnv = { ...process.env };
|
|
2064
|
+
Object.assign(process.env, mergedEnv);
|
|
2065
|
+
const log = createContextLogger();
|
|
2066
|
+
const app = context.app ?? {
|
|
2067
|
+
id: context.appId,
|
|
2068
|
+
versionId: context.appId
|
|
2069
|
+
};
|
|
2070
|
+
const workplace = context.workplace ?? {
|
|
2071
|
+
id: context.workplaceId,
|
|
2072
|
+
subdomain: ""
|
|
2073
|
+
};
|
|
2074
|
+
const invocation = body.invocation ?? {
|
|
2075
|
+
invocationId: `${body.handle}-${body.method}-${Date.now()}`,
|
|
2076
|
+
invocationType: "batch_operation",
|
|
2077
|
+
batchOperationHandle: body.handle,
|
|
2078
|
+
batchOperationMethod: body.method,
|
|
2079
|
+
appInstallationId: context.appInstallationId
|
|
2038
2080
|
};
|
|
2039
2081
|
const requestConfig = {
|
|
2040
|
-
baseUrl:
|
|
2041
|
-
apiToken:
|
|
2082
|
+
baseUrl: mergedEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
|
|
2083
|
+
apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
|
|
2084
|
+
};
|
|
2085
|
+
const rateLimitContext = {
|
|
2086
|
+
app,
|
|
2087
|
+
appInstallationId: context.appInstallationId,
|
|
2088
|
+
invocation,
|
|
2089
|
+
preAcquiredLeases: parsePreAcquiredLeases(
|
|
2090
|
+
mergedEnv.SKEDYUL_RATE_LIMIT_LEASES
|
|
2091
|
+
),
|
|
2092
|
+
heldMutexQueueKeys: parseHeldMutexQueueKeys(
|
|
2093
|
+
mergedEnv.SKEDYUL_HELD_MUTEX_QUEUE_KEYS
|
|
2094
|
+
)
|
|
2095
|
+
};
|
|
2096
|
+
const opContextBase = {
|
|
2097
|
+
workplaceId: context.workplaceId,
|
|
2098
|
+
appInstallationId: context.appInstallationId,
|
|
2099
|
+
appId: context.appId,
|
|
2100
|
+
app,
|
|
2101
|
+
workplace,
|
|
2102
|
+
input: body.input,
|
|
2103
|
+
env: mergedEnv,
|
|
2104
|
+
invocation,
|
|
2105
|
+
log
|
|
2042
2106
|
};
|
|
2043
2107
|
try {
|
|
2044
2108
|
if (body.method === "setup") {
|
|
@@ -2047,67 +2111,88 @@ async function handleBatchOperationRoute(req, ctx) {
|
|
|
2047
2111
|
status: 200,
|
|
2048
2112
|
body: {
|
|
2049
2113
|
success: true,
|
|
2050
|
-
result: { state: {}, total: void 0 }
|
|
2114
|
+
result: { state: {}, total: void 0 },
|
|
2115
|
+
billing: normalizeBilling(void 0)
|
|
2051
2116
|
}
|
|
2052
2117
|
};
|
|
2053
2118
|
}
|
|
2054
|
-
const
|
|
2055
|
-
return
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
input: body.input,
|
|
2060
|
-
env,
|
|
2061
|
-
log
|
|
2119
|
+
const raw2 = await runWithConfig(requestConfig, async () => {
|
|
2120
|
+
return runWithRateLimitExecutionContext(rateLimitContext, async () => {
|
|
2121
|
+
return runWithLogContext({ invocation }, async () => {
|
|
2122
|
+
return operation.setup(opContextBase);
|
|
2123
|
+
});
|
|
2062
2124
|
});
|
|
2063
2125
|
});
|
|
2126
|
+
if (isBatchOperationFailure(raw2)) {
|
|
2127
|
+
return softFailureResponse(raw2.error, raw2.retry, raw2.billing);
|
|
2128
|
+
}
|
|
2129
|
+
const setupResult = raw2;
|
|
2130
|
+
const domain2 = stripBillingFields(
|
|
2131
|
+
setupResult
|
|
2132
|
+
);
|
|
2064
2133
|
return {
|
|
2065
2134
|
status: 200,
|
|
2066
2135
|
body: {
|
|
2067
2136
|
success: true,
|
|
2068
|
-
result
|
|
2137
|
+
result: domain2,
|
|
2138
|
+
billing: normalizeBilling(setupResult.billing)
|
|
2069
2139
|
}
|
|
2070
2140
|
};
|
|
2071
2141
|
}
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
return
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
limit: body.limit,
|
|
2083
|
-
env,
|
|
2084
|
-
log
|
|
2142
|
+
const raw = await runWithConfig(requestConfig, async () => {
|
|
2143
|
+
return runWithRateLimitExecutionContext(rateLimitContext, async () => {
|
|
2144
|
+
return runWithLogContext({ invocation }, async () => {
|
|
2145
|
+
return operation.iterate({
|
|
2146
|
+
...opContextBase,
|
|
2147
|
+
state: body.state,
|
|
2148
|
+
page: body.page,
|
|
2149
|
+
cursor: body.cursor,
|
|
2150
|
+
limit: body.limit
|
|
2151
|
+
});
|
|
2085
2152
|
});
|
|
2086
2153
|
});
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
success: true,
|
|
2091
|
-
result
|
|
2092
|
-
}
|
|
2093
|
-
};
|
|
2154
|
+
});
|
|
2155
|
+
if (isBatchOperationFailure(raw)) {
|
|
2156
|
+
return softFailureResponse(raw.error, raw.retry, raw.billing);
|
|
2094
2157
|
}
|
|
2158
|
+
const iterateResult = raw;
|
|
2159
|
+
const domain = stripBillingFields(
|
|
2160
|
+
iterateResult
|
|
2161
|
+
);
|
|
2095
2162
|
return {
|
|
2096
|
-
status:
|
|
2163
|
+
status: 200,
|
|
2097
2164
|
body: {
|
|
2098
|
-
success:
|
|
2099
|
-
|
|
2165
|
+
success: true,
|
|
2166
|
+
result: domain,
|
|
2167
|
+
billing: normalizeBilling(iterateResult.billing)
|
|
2100
2168
|
}
|
|
2101
2169
|
};
|
|
2102
2170
|
} catch (err) {
|
|
2103
|
-
|
|
2171
|
+
if (err instanceof RateLimitExceededError) {
|
|
2172
|
+
return softFailureResponse(
|
|
2173
|
+
{
|
|
2174
|
+
code: "RATE_LIMITED",
|
|
2175
|
+
message: err.message,
|
|
2176
|
+
category: "external"
|
|
2177
|
+
},
|
|
2178
|
+
{ allowed: true, afterMs: err.retryAfterMs }
|
|
2179
|
+
);
|
|
2180
|
+
}
|
|
2181
|
+
log.error("Batch operation failed", err);
|
|
2104
2182
|
return {
|
|
2105
2183
|
status: 500,
|
|
2106
2184
|
body: {
|
|
2107
2185
|
success: false,
|
|
2108
|
-
error:
|
|
2186
|
+
error: {
|
|
2187
|
+
code: "INTERNAL_ERROR",
|
|
2188
|
+
message: err instanceof Error ? err.message : String(err),
|
|
2189
|
+
category: "internal"
|
|
2190
|
+
},
|
|
2191
|
+
billing: normalizeBilling(void 0)
|
|
2109
2192
|
}
|
|
2110
2193
|
};
|
|
2194
|
+
} finally {
|
|
2195
|
+
process.env = originalEnv;
|
|
2111
2196
|
}
|
|
2112
2197
|
}
|
|
2113
2198
|
function createNotFoundResponse() {
|