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.
- package/dist/.build-stamp +1 -1
- package/dist/cli/index.js +161 -55
- package/dist/config/define.d.ts +2 -0
- package/dist/config/types/batch-operation.d.ts +38 -2
- package/dist/dedicated/server.js +161 -55
- package/dist/esm/index.mjs +173 -55
- package/dist/index.js +174 -55
- package/dist/server/route-handlers/handlers.d.ts +3 -0
- package/dist/server.js +161 -55
- package/dist/serverless/server.mjs +161 -55
- 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,34 +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
|
-
const
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
}
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4716
|
+
const context = body.context;
|
|
4717
|
+
if (!context?.workplaceId || !context?.appInstallationId || !context?.appId) {
|
|
4718
|
+
return softFailureResponse({
|
|
4719
|
+
code: "VALIDATION_ERROR",
|
|
4720
|
+
message: "Missing context (workplaceId, appInstallationId, and appId required)",
|
|
4721
|
+
category: "validation"
|
|
4722
|
+
});
|
|
4723
|
+
}
|
|
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
|
|
4750
|
+
};
|
|
4751
|
+
const requestConfig = {
|
|
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
|
|
4697
4776
|
};
|
|
4698
4777
|
try {
|
|
4699
4778
|
if (body.method === "setup") {
|
|
@@ -4702,61 +4781,88 @@ async function handleBatchOperationRoute(req, ctx) {
|
|
|
4702
4781
|
status: 200,
|
|
4703
4782
|
body: {
|
|
4704
4783
|
success: true,
|
|
4705
|
-
result: { state: {}, total: void 0 }
|
|
4784
|
+
result: { state: {}, total: void 0 },
|
|
4785
|
+
billing: normalizeBilling(void 0)
|
|
4706
4786
|
}
|
|
4707
4787
|
};
|
|
4708
4788
|
}
|
|
4709
|
-
const
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4789
|
+
const raw2 = await runWithConfig(requestConfig, async () => {
|
|
4790
|
+
return runWithRateLimitExecutionContext(rateLimitContext, async () => {
|
|
4791
|
+
return runWithLogContext({ invocation }, async () => {
|
|
4792
|
+
return operation.setup(opContextBase);
|
|
4793
|
+
});
|
|
4794
|
+
});
|
|
4715
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
|
+
);
|
|
4716
4803
|
return {
|
|
4717
4804
|
status: 200,
|
|
4718
4805
|
body: {
|
|
4719
4806
|
success: true,
|
|
4720
|
-
result
|
|
4807
|
+
result: domain2,
|
|
4808
|
+
billing: normalizeBilling(setupResult.billing)
|
|
4721
4809
|
}
|
|
4722
4810
|
};
|
|
4723
4811
|
}
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
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
|
+
});
|
|
4822
|
+
});
|
|
4735
4823
|
});
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
success: true,
|
|
4740
|
-
result
|
|
4741
|
-
}
|
|
4742
|
-
};
|
|
4824
|
+
});
|
|
4825
|
+
if (isBatchOperationFailure(raw)) {
|
|
4826
|
+
return softFailureResponse(raw.error, raw.retry, raw.billing);
|
|
4743
4827
|
}
|
|
4828
|
+
const iterateResult = raw;
|
|
4829
|
+
const domain = stripBillingFields(
|
|
4830
|
+
iterateResult
|
|
4831
|
+
);
|
|
4744
4832
|
return {
|
|
4745
|
-
status:
|
|
4833
|
+
status: 200,
|
|
4746
4834
|
body: {
|
|
4747
|
-
success:
|
|
4748
|
-
|
|
4835
|
+
success: true,
|
|
4836
|
+
result: domain,
|
|
4837
|
+
billing: normalizeBilling(iterateResult.billing)
|
|
4749
4838
|
}
|
|
4750
4839
|
};
|
|
4751
4840
|
} catch (err) {
|
|
4752
|
-
|
|
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);
|
|
4753
4852
|
return {
|
|
4754
4853
|
status: 500,
|
|
4755
4854
|
body: {
|
|
4756
4855
|
success: false,
|
|
4757
|
-
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)
|
|
4758
4862
|
}
|
|
4759
4863
|
};
|
|
4864
|
+
} finally {
|
|
4865
|
+
process.env = originalEnv;
|
|
4760
4866
|
}
|
|
4761
4867
|
}
|
|
4762
4868
|
function createNotFoundResponse() {
|
package/dist/config/define.d.ts
CHANGED
|
@@ -183,7 +183,9 @@ export declare function defineSetupStep(step: SetupStepDefinition): SetupStepDef
|
|
|
183
183
|
* return { total: count }
|
|
184
184
|
* },
|
|
185
185
|
* iterate: async (ctx) => {
|
|
186
|
+
* const apiKey = ctx.env.GLOFOX_API_KEY
|
|
186
187
|
* const { data, pagination } = await glofox.getMembers({
|
|
188
|
+
* apiKey,
|
|
187
189
|
* limit: ctx.limit,
|
|
188
190
|
* offset: ctx.cursor ?? 0,
|
|
189
191
|
* })
|
|
@@ -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,10 +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>;
|
|
85
|
+
/**
|
|
86
|
+
* Merged environment (process.env + baked MCP_ENV + request env),
|
|
87
|
+
* same layering as tool handlers via buildToolExecutionEnv.
|
|
88
|
+
*/
|
|
89
|
+
env: Record<string, string>;
|
|
90
|
+
/** Invocation context for log traceability */
|
|
91
|
+
invocation?: InvocationContext;
|
|
58
92
|
/** Logger instance */
|
|
59
93
|
log: {
|
|
60
94
|
info: (message: string, meta?: Record<string, unknown>) => void;
|
|
@@ -65,7 +99,7 @@ export interface BatchOperationContext {
|
|
|
65
99
|
/**
|
|
66
100
|
* Type for batch operation setup function.
|
|
67
101
|
*/
|
|
68
|
-
export type BatchOperationSetupFn = (ctx: BatchOperationContext) => Promise<
|
|
102
|
+
export type BatchOperationSetupFn = (ctx: BatchOperationContext) => Promise<BatchOperationSetupReturn>;
|
|
69
103
|
/**
|
|
70
104
|
* Type for batch operation iterate function.
|
|
71
105
|
*/
|
|
@@ -76,7 +110,7 @@ export type BatchOperationIterateFn = (ctx: BatchOperationContext & {
|
|
|
76
110
|
cursor?: string | number;
|
|
77
111
|
/** Items per page */
|
|
78
112
|
limit: number;
|
|
79
|
-
}) => Promise<
|
|
113
|
+
}) => Promise<BatchOperationIterateReturn>;
|
|
80
114
|
/**
|
|
81
115
|
* Batch operation definition.
|
|
82
116
|
*
|
|
@@ -90,7 +124,9 @@ export type BatchOperationIterateFn = (ctx: BatchOperationContext & {
|
|
|
90
124
|
* return { total: count }
|
|
91
125
|
* },
|
|
92
126
|
* iterate: async (ctx) => {
|
|
127
|
+
* const apiKey = ctx.env.GLOFOX_API_KEY
|
|
93
128
|
* const { data, pagination } = await glofox.getMembers({
|
|
129
|
+
* apiKey,
|
|
94
130
|
* limit: ctx.limit,
|
|
95
131
|
* offset: ctx.cursor ?? 0,
|
|
96
132
|
* })
|
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,34 +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
|
-
const
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
}
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2046
|
+
const context = body.context;
|
|
2047
|
+
if (!context?.workplaceId || !context?.appInstallationId || !context?.appId) {
|
|
2048
|
+
return softFailureResponse({
|
|
2049
|
+
code: "VALIDATION_ERROR",
|
|
2050
|
+
message: "Missing context (workplaceId, appInstallationId, and appId required)",
|
|
2051
|
+
category: "validation"
|
|
2052
|
+
});
|
|
2053
|
+
}
|
|
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
|
|
2080
|
+
};
|
|
2081
|
+
const requestConfig = {
|
|
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
|
|
2027
2106
|
};
|
|
2028
2107
|
try {
|
|
2029
2108
|
if (body.method === "setup") {
|
|
@@ -2032,61 +2111,88 @@ async function handleBatchOperationRoute(req, ctx) {
|
|
|
2032
2111
|
status: 200,
|
|
2033
2112
|
body: {
|
|
2034
2113
|
success: true,
|
|
2035
|
-
result: { state: {}, total: void 0 }
|
|
2114
|
+
result: { state: {}, total: void 0 },
|
|
2115
|
+
billing: normalizeBilling(void 0)
|
|
2036
2116
|
}
|
|
2037
2117
|
};
|
|
2038
2118
|
}
|
|
2039
|
-
const
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2119
|
+
const raw2 = await runWithConfig(requestConfig, async () => {
|
|
2120
|
+
return runWithRateLimitExecutionContext(rateLimitContext, async () => {
|
|
2121
|
+
return runWithLogContext({ invocation }, async () => {
|
|
2122
|
+
return operation.setup(opContextBase);
|
|
2123
|
+
});
|
|
2124
|
+
});
|
|
2045
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
|
+
);
|
|
2046
2133
|
return {
|
|
2047
2134
|
status: 200,
|
|
2048
2135
|
body: {
|
|
2049
2136
|
success: true,
|
|
2050
|
-
result
|
|
2137
|
+
result: domain2,
|
|
2138
|
+
billing: normalizeBilling(setupResult.billing)
|
|
2051
2139
|
}
|
|
2052
2140
|
};
|
|
2053
2141
|
}
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
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
|
+
});
|
|
2152
|
+
});
|
|
2065
2153
|
});
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
success: true,
|
|
2070
|
-
result
|
|
2071
|
-
}
|
|
2072
|
-
};
|
|
2154
|
+
});
|
|
2155
|
+
if (isBatchOperationFailure(raw)) {
|
|
2156
|
+
return softFailureResponse(raw.error, raw.retry, raw.billing);
|
|
2073
2157
|
}
|
|
2158
|
+
const iterateResult = raw;
|
|
2159
|
+
const domain = stripBillingFields(
|
|
2160
|
+
iterateResult
|
|
2161
|
+
);
|
|
2074
2162
|
return {
|
|
2075
|
-
status:
|
|
2163
|
+
status: 200,
|
|
2076
2164
|
body: {
|
|
2077
|
-
success:
|
|
2078
|
-
|
|
2165
|
+
success: true,
|
|
2166
|
+
result: domain,
|
|
2167
|
+
billing: normalizeBilling(iterateResult.billing)
|
|
2079
2168
|
}
|
|
2080
2169
|
};
|
|
2081
2170
|
} catch (err) {
|
|
2082
|
-
|
|
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);
|
|
2083
2182
|
return {
|
|
2084
2183
|
status: 500,
|
|
2085
2184
|
body: {
|
|
2086
2185
|
success: false,
|
|
2087
|
-
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)
|
|
2088
2192
|
}
|
|
2089
2193
|
};
|
|
2194
|
+
} finally {
|
|
2195
|
+
process.env = originalEnv;
|
|
2090
2196
|
}
|
|
2091
2197
|
}
|
|
2092
2198
|
function createNotFoundResponse() {
|