@decocms/runtime 1.3.0 → 1.3.1
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/package.json +1 -1
- package/src/tools.ts +178 -118
- package/src/workflows.ts +50 -11
package/package.json
CHANGED
package/src/tools.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
9
9
|
import { WebStandardStreamableHTTPServerTransport as HttpServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
10
10
|
import type {
|
|
11
11
|
GetPromptResult,
|
|
12
|
+
Implementation,
|
|
12
13
|
ToolAnnotations,
|
|
13
14
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
14
15
|
import { z } from "zod";
|
|
@@ -485,6 +486,7 @@ export interface CreateMCPServerOptions<
|
|
|
485
486
|
State extends
|
|
486
487
|
TEnv["MESH_REQUEST_CONTEXT"]["state"] = TEnv["MESH_REQUEST_CONTEXT"]["state"],
|
|
487
488
|
> {
|
|
489
|
+
serverInfo?: Partial<Implementation> & { instructions?: string };
|
|
488
490
|
before?: (env: TEnv) => Promise<void> | void;
|
|
489
491
|
oauth?: OAuthConfig;
|
|
490
492
|
events?: {
|
|
@@ -711,14 +713,21 @@ const toolsFor = <TSchema extends ZodTypeAny = never>({
|
|
|
711
713
|
...(workflows?.length
|
|
712
714
|
? workflows.map((wf) => {
|
|
713
715
|
const id = wf.toolId ?? workflowToolId(wf.title);
|
|
716
|
+
const baseDescription = [
|
|
717
|
+
wf.description
|
|
718
|
+
? `Run workflow: ${wf.description}`
|
|
719
|
+
: `Start the "${wf.title}" workflow.`,
|
|
720
|
+
"Returns an execution_id immediately. Use COLLECTION_WORKFLOW_EXECUTION_GET to track progress.",
|
|
721
|
+
].join(" ");
|
|
714
722
|
return createTool({
|
|
715
723
|
id,
|
|
716
|
-
description:
|
|
717
|
-
wf.
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
724
|
+
description: (() => {
|
|
725
|
+
if (!wf.inputSchema) return baseDescription;
|
|
726
|
+
const schemaStr = JSON.stringify(wf.inputSchema, null, 2);
|
|
727
|
+
return schemaStr.length <= 2048
|
|
728
|
+
? `${baseDescription}\n\nInput schema:\n${schemaStr}`
|
|
729
|
+
: `${baseDescription}\n\nThis workflow expects structured input. Use COLLECTION_WORKFLOW_GET to inspect the full input schema.`;
|
|
730
|
+
})(),
|
|
722
731
|
inputSchema: z.object({
|
|
723
732
|
input: z
|
|
724
733
|
.record(z.string(), z.unknown())
|
|
@@ -808,44 +817,118 @@ export const createMCPServer = <
|
|
|
808
817
|
>(
|
|
809
818
|
options: CreateMCPServerOptions<TEnv, TSchema, TBindings>,
|
|
810
819
|
): MCPServer<TEnv, TSchema, TBindings> => {
|
|
811
|
-
|
|
812
|
-
|
|
820
|
+
// Tool/prompt/resource definitions are resolved once on first request and
|
|
821
|
+
// cached for the lifetime of the process. Tool *execution* reads per-request
|
|
822
|
+
// context from State (AsyncLocalStorage), so reusing definitions is safe.
|
|
823
|
+
type Registrations = {
|
|
824
|
+
tools: CreatedTool[];
|
|
825
|
+
prompts: CreatedPrompt[];
|
|
826
|
+
resources: CreatedResource[];
|
|
827
|
+
workflows?: WorkflowDefinition[];
|
|
828
|
+
};
|
|
813
829
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
{ capabilities: { tools: {}, prompts: {}, resources: {} } },
|
|
817
|
-
);
|
|
830
|
+
let cached: Registrations | null = null;
|
|
831
|
+
let inflightResolve: Promise<Registrations> | null = null;
|
|
818
832
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
return await options.tools(bindings);
|
|
825
|
-
}
|
|
826
|
-
return await Promise.all(
|
|
827
|
-
options.tools?.flatMap(async (tool) => {
|
|
828
|
-
const toolResult = tool(bindings);
|
|
829
|
-
const awaited = await toolResult;
|
|
830
|
-
if (Array.isArray(awaited)) {
|
|
831
|
-
return awaited;
|
|
832
|
-
}
|
|
833
|
-
return [awaited];
|
|
834
|
-
}) ?? [],
|
|
835
|
-
).then((t) => t.flat());
|
|
836
|
-
};
|
|
837
|
-
const tools = await toolsFn(bindings);
|
|
833
|
+
const resolveRegistrations = async (
|
|
834
|
+
bindings: TEnv,
|
|
835
|
+
): Promise<Registrations> => {
|
|
836
|
+
if (cached) return cached;
|
|
837
|
+
if (inflightResolve) return inflightResolve;
|
|
838
838
|
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
839
|
+
inflightResolve = (async (): Promise<Registrations> => {
|
|
840
|
+
try {
|
|
841
|
+
const toolsFn =
|
|
842
|
+
typeof options.tools === "function"
|
|
843
|
+
? options.tools
|
|
844
|
+
: async (bindings: TEnv) => {
|
|
845
|
+
if (typeof options.tools === "function") {
|
|
846
|
+
return await options.tools(bindings);
|
|
847
|
+
}
|
|
848
|
+
return await Promise.all(
|
|
849
|
+
options.tools?.flatMap(async (tool) => {
|
|
850
|
+
const toolResult = tool(bindings);
|
|
851
|
+
const awaited = await toolResult;
|
|
852
|
+
if (Array.isArray(awaited)) {
|
|
853
|
+
return awaited;
|
|
854
|
+
}
|
|
855
|
+
return [awaited];
|
|
856
|
+
}) ?? [],
|
|
857
|
+
).then((t) => t.flat());
|
|
858
|
+
};
|
|
859
|
+
const tools = await toolsFn(bindings);
|
|
860
|
+
|
|
861
|
+
const resolvedWorkflows =
|
|
862
|
+
typeof options.workflows === "function"
|
|
863
|
+
? await options.workflows(bindings)
|
|
864
|
+
: options.workflows;
|
|
865
|
+
|
|
866
|
+
tools.push(
|
|
867
|
+
...toolsFor<TSchema>({ ...options, workflows: resolvedWorkflows }),
|
|
868
|
+
);
|
|
869
|
+
|
|
870
|
+
const promptsFn =
|
|
871
|
+
typeof options.prompts === "function"
|
|
872
|
+
? options.prompts
|
|
873
|
+
: async (bindings: TEnv) => {
|
|
874
|
+
if (typeof options.prompts === "function") {
|
|
875
|
+
return await options.prompts(bindings);
|
|
876
|
+
}
|
|
877
|
+
return await Promise.all(
|
|
878
|
+
options.prompts?.flatMap(async (prompt) => {
|
|
879
|
+
const promptResult = prompt(bindings);
|
|
880
|
+
const awaited = await promptResult;
|
|
881
|
+
if (Array.isArray(awaited)) {
|
|
882
|
+
return awaited;
|
|
883
|
+
}
|
|
884
|
+
return [awaited];
|
|
885
|
+
}) ?? [],
|
|
886
|
+
).then((p) => p.flat());
|
|
887
|
+
};
|
|
888
|
+
const prompts = await promptsFn(bindings);
|
|
889
|
+
|
|
890
|
+
const resourcesFn =
|
|
891
|
+
typeof options.resources === "function"
|
|
892
|
+
? options.resources
|
|
893
|
+
: async (bindings: TEnv) => {
|
|
894
|
+
if (typeof options.resources === "function") {
|
|
895
|
+
return await options.resources(bindings);
|
|
896
|
+
}
|
|
897
|
+
return await Promise.all(
|
|
898
|
+
options.resources?.flatMap(async (resource) => {
|
|
899
|
+
const resourceResult = resource(bindings);
|
|
900
|
+
const awaited = await resourceResult;
|
|
901
|
+
if (Array.isArray(awaited)) {
|
|
902
|
+
return awaited;
|
|
903
|
+
}
|
|
904
|
+
return [awaited];
|
|
905
|
+
}) ?? [],
|
|
906
|
+
).then((r) => r.flat());
|
|
907
|
+
};
|
|
908
|
+
const resources = await resourcesFn(bindings);
|
|
909
|
+
|
|
910
|
+
const result = {
|
|
911
|
+
tools,
|
|
912
|
+
prompts,
|
|
913
|
+
resources,
|
|
914
|
+
workflows: resolvedWorkflows,
|
|
915
|
+
};
|
|
916
|
+
cached = result;
|
|
917
|
+
return result;
|
|
918
|
+
} catch (err) {
|
|
919
|
+
inflightResolve = null;
|
|
920
|
+
throw err;
|
|
921
|
+
}
|
|
922
|
+
})();
|
|
843
923
|
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
);
|
|
924
|
+
return inflightResolve;
|
|
925
|
+
};
|
|
847
926
|
|
|
848
|
-
|
|
927
|
+
const registerAll = (
|
|
928
|
+
server: McpServer,
|
|
929
|
+
registrations: Registrations,
|
|
930
|
+
) => {
|
|
931
|
+
for (const tool of registrations.tools) {
|
|
849
932
|
server.registerTool(
|
|
850
933
|
tool.id,
|
|
851
934
|
{
|
|
@@ -873,9 +956,8 @@ export const createMCPServer = <
|
|
|
873
956
|
runtimeContext: createRuntimeContext(),
|
|
874
957
|
});
|
|
875
958
|
|
|
876
|
-
// For streamable tools, the Response is handled at the transport layer
|
|
877
|
-
// Do NOT call result.bytes()
|
|
878
|
-
// causing massive memory leaks (2GB+ Uint8Array accumulation)
|
|
959
|
+
// For streamable tools, the Response is handled at the transport layer.
|
|
960
|
+
// Do NOT call result.bytes() — it buffers the entire body in memory.
|
|
879
961
|
if (isStreamableTool(tool) && result instanceof Response) {
|
|
880
962
|
return {
|
|
881
963
|
structuredContent: {
|
|
@@ -904,28 +986,7 @@ export const createMCPServer = <
|
|
|
904
986
|
);
|
|
905
987
|
}
|
|
906
988
|
|
|
907
|
-
|
|
908
|
-
const promptsFn =
|
|
909
|
-
typeof options.prompts === "function"
|
|
910
|
-
? options.prompts
|
|
911
|
-
: async (bindings: TEnv) => {
|
|
912
|
-
if (typeof options.prompts === "function") {
|
|
913
|
-
return await options.prompts(bindings);
|
|
914
|
-
}
|
|
915
|
-
return await Promise.all(
|
|
916
|
-
options.prompts?.flatMap(async (prompt) => {
|
|
917
|
-
const promptResult = prompt(bindings);
|
|
918
|
-
const awaited = await promptResult;
|
|
919
|
-
if (Array.isArray(awaited)) {
|
|
920
|
-
return awaited;
|
|
921
|
-
}
|
|
922
|
-
return [awaited];
|
|
923
|
-
}) ?? [],
|
|
924
|
-
).then((p) => p.flat());
|
|
925
|
-
};
|
|
926
|
-
const prompts = await promptsFn(bindings);
|
|
927
|
-
|
|
928
|
-
for (const prompt of prompts) {
|
|
989
|
+
for (const prompt of registrations.prompts) {
|
|
929
990
|
server.registerPrompt(
|
|
930
991
|
prompt.name,
|
|
931
992
|
{
|
|
@@ -944,28 +1005,7 @@ export const createMCPServer = <
|
|
|
944
1005
|
);
|
|
945
1006
|
}
|
|
946
1007
|
|
|
947
|
-
|
|
948
|
-
const resourcesFn =
|
|
949
|
-
typeof options.resources === "function"
|
|
950
|
-
? options.resources
|
|
951
|
-
: async (bindings: TEnv) => {
|
|
952
|
-
if (typeof options.resources === "function") {
|
|
953
|
-
return await options.resources(bindings);
|
|
954
|
-
}
|
|
955
|
-
return await Promise.all(
|
|
956
|
-
options.resources?.flatMap(async (resource) => {
|
|
957
|
-
const resourceResult = resource(bindings);
|
|
958
|
-
const awaited = await resourceResult;
|
|
959
|
-
if (Array.isArray(awaited)) {
|
|
960
|
-
return awaited;
|
|
961
|
-
}
|
|
962
|
-
return [awaited];
|
|
963
|
-
}) ?? [],
|
|
964
|
-
).then((r) => r.flat());
|
|
965
|
-
};
|
|
966
|
-
const resources = await resourcesFn(bindings);
|
|
967
|
-
|
|
968
|
-
for (const resource of resources) {
|
|
1008
|
+
for (const resource of registrations.resources) {
|
|
969
1009
|
server.resource(
|
|
970
1010
|
resource.name,
|
|
971
1011
|
resource.uri,
|
|
@@ -978,19 +1018,7 @@ export const createMCPServer = <
|
|
|
978
1018
|
uri,
|
|
979
1019
|
runtimeContext: createRuntimeContext(),
|
|
980
1020
|
});
|
|
981
|
-
// Build content object based on what's provided (text or blob, not both)
|
|
982
|
-
const content: {
|
|
983
|
-
uri: string;
|
|
984
|
-
mimeType?: string;
|
|
985
|
-
text?: string;
|
|
986
|
-
blob?: string;
|
|
987
|
-
} = { uri: result.uri };
|
|
988
|
-
|
|
989
|
-
if (result.mimeType) {
|
|
990
|
-
content.mimeType = result.mimeType;
|
|
991
|
-
}
|
|
992
1021
|
|
|
993
|
-
// MCP SDK expects either text or blob content, not both
|
|
994
1022
|
const meta =
|
|
995
1023
|
(result as { _meta?: Record<string, unknown> | null })._meta ??
|
|
996
1024
|
undefined;
|
|
@@ -1018,7 +1046,6 @@ export const createMCPServer = <
|
|
|
1018
1046
|
};
|
|
1019
1047
|
}
|
|
1020
1048
|
|
|
1021
|
-
// Fallback to empty text if neither provided
|
|
1022
1049
|
return {
|
|
1023
1050
|
contents: [
|
|
1024
1051
|
{ uri: result.uri, mimeType: result.mimeType, text: "" },
|
|
@@ -1027,8 +1054,28 @@ export const createMCPServer = <
|
|
|
1027
1054
|
},
|
|
1028
1055
|
);
|
|
1029
1056
|
}
|
|
1057
|
+
};
|
|
1058
|
+
|
|
1059
|
+
const createServer = async (bindings: TEnv) => {
|
|
1060
|
+
await options.before?.(bindings);
|
|
1061
|
+
|
|
1062
|
+
const { instructions, ...serverInfoOverrides } = options.serverInfo ?? {};
|
|
1063
|
+
const server = new McpServer(
|
|
1064
|
+
{
|
|
1065
|
+
...serverInfoOverrides,
|
|
1066
|
+
name: serverInfoOverrides.name ?? "@deco/mcp-api",
|
|
1067
|
+
version: serverInfoOverrides.version ?? "1.0.0",
|
|
1068
|
+
},
|
|
1069
|
+
{
|
|
1070
|
+
capabilities: { tools: {}, prompts: {}, resources: {} },
|
|
1071
|
+
...(instructions && { instructions }),
|
|
1072
|
+
},
|
|
1073
|
+
);
|
|
1030
1074
|
|
|
1031
|
-
|
|
1075
|
+
const registrations = await resolveRegistrations(bindings);
|
|
1076
|
+
registerAll(server, registrations);
|
|
1077
|
+
|
|
1078
|
+
return { server, ...registrations };
|
|
1032
1079
|
};
|
|
1033
1080
|
|
|
1034
1081
|
const fetch = async (req: Request, env: TEnv) => {
|
|
@@ -1037,34 +1084,45 @@ export const createMCPServer = <
|
|
|
1037
1084
|
|
|
1038
1085
|
await server.connect(transport);
|
|
1039
1086
|
|
|
1087
|
+
const cleanup = () => {
|
|
1088
|
+
try {
|
|
1089
|
+
transport.close?.();
|
|
1090
|
+
} catch {
|
|
1091
|
+
/* ignore */
|
|
1092
|
+
}
|
|
1093
|
+
try {
|
|
1094
|
+
server.close?.();
|
|
1095
|
+
} catch {
|
|
1096
|
+
/* ignore */
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
|
|
1040
1100
|
try {
|
|
1041
1101
|
const response = await transport.handleRequest(req);
|
|
1042
1102
|
|
|
1043
|
-
// Check if this is a streaming response (SSE or streamable tool)
|
|
1044
|
-
// SSE responses have text/event-stream content-type
|
|
1045
|
-
// Note: response.body is always non-null for all HTTP responses, so we can't use it to detect streaming
|
|
1046
1103
|
const contentType = response.headers.get("content-type");
|
|
1047
1104
|
const isStreaming =
|
|
1048
1105
|
contentType?.includes("text/event-stream") ||
|
|
1049
1106
|
contentType?.includes("application/json-rpc");
|
|
1050
1107
|
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
await transport.close?.();
|
|
1055
|
-
} catch {
|
|
1056
|
-
// Ignore close errors
|
|
1057
|
-
}
|
|
1108
|
+
if (!isStreaming || !response.body) {
|
|
1109
|
+
cleanup();
|
|
1110
|
+
return response;
|
|
1058
1111
|
}
|
|
1059
1112
|
|
|
1060
|
-
|
|
1113
|
+
// Pipe the SSE body through a passthrough so that when the stream
|
|
1114
|
+
// finishes (server sent the response) or the client disconnects
|
|
1115
|
+
// (cancel), the server and transport are always cleaned up.
|
|
1116
|
+
const { readable, writable } = new TransformStream();
|
|
1117
|
+
response.body.pipeTo(writable).catch(() => {}).finally(cleanup);
|
|
1118
|
+
|
|
1119
|
+
return new Response(readable, {
|
|
1120
|
+
status: response.status,
|
|
1121
|
+
statusText: response.statusText,
|
|
1122
|
+
headers: response.headers,
|
|
1123
|
+
});
|
|
1061
1124
|
} catch (error) {
|
|
1062
|
-
|
|
1063
|
-
try {
|
|
1064
|
-
await transport.close?.();
|
|
1065
|
-
} catch {
|
|
1066
|
-
// Ignore close errors
|
|
1067
|
-
}
|
|
1125
|
+
cleanup();
|
|
1068
1126
|
throw error;
|
|
1069
1127
|
}
|
|
1070
1128
|
};
|
|
@@ -1075,7 +1133,9 @@ export const createMCPServer = <
|
|
|
1075
1133
|
throw new Error("Missing state, did you forget to call State.bind?");
|
|
1076
1134
|
}
|
|
1077
1135
|
const env = currentState?.env;
|
|
1078
|
-
const { tools } = await
|
|
1136
|
+
const { tools } = await resolveRegistrations(
|
|
1137
|
+
env as TEnv & DefaultEnv<TSchema>,
|
|
1138
|
+
);
|
|
1079
1139
|
const tool = tools.find((t) => t.id === toolCallId);
|
|
1080
1140
|
const execute = tool?.execute;
|
|
1081
1141
|
if (!execute) {
|
package/src/workflows.ts
CHANGED
|
@@ -24,6 +24,12 @@ export interface WorkflowDefinition {
|
|
|
24
24
|
* Defaults to START_WORKFLOW_<TITLE_SLUG> (e.g. START_WORKFLOW_FETCH_USERS).
|
|
25
25
|
*/
|
|
26
26
|
toolId?: string;
|
|
27
|
+
/**
|
|
28
|
+
* JSON Schema describing the expected input for this workflow.
|
|
29
|
+
* When set, the mesh validates execution input against this schema
|
|
30
|
+
* before creating an execution.
|
|
31
|
+
*/
|
|
32
|
+
inputSchema?: Record<string, unknown> | null;
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
interface WorkflowCollectionItem {
|
|
@@ -67,6 +73,7 @@ interface MeshWorkflowClient {
|
|
|
67
73
|
description?: string;
|
|
68
74
|
virtual_mcp_id?: string;
|
|
69
75
|
steps: Step[];
|
|
76
|
+
input_schema?: Record<string, unknown> | null;
|
|
70
77
|
};
|
|
71
78
|
}) => Promise<{ item: WorkflowCollectionItem }>;
|
|
72
79
|
COLLECTION_WORKFLOW_UPDATE: (input: {
|
|
@@ -76,6 +83,7 @@ interface MeshWorkflowClient {
|
|
|
76
83
|
description?: string;
|
|
77
84
|
virtual_mcp_id?: string;
|
|
78
85
|
steps?: Step[];
|
|
86
|
+
input_schema?: Record<string, unknown> | null;
|
|
79
87
|
};
|
|
80
88
|
}) => Promise<{ success: boolean; error?: string }>;
|
|
81
89
|
COLLECTION_WORKFLOW_DELETE: (input: {
|
|
@@ -254,10 +262,11 @@ function fingerprintWorkflows(declared: WorkflowDefinition[]): string {
|
|
|
254
262
|
return JSON.stringify(
|
|
255
263
|
declared.map((w) => ({
|
|
256
264
|
title: w.title,
|
|
257
|
-
description: w.description ??
|
|
258
|
-
virtual_mcp_id: w.virtual_mcp_id ??
|
|
265
|
+
description: w.description ?? undefined,
|
|
266
|
+
virtual_mcp_id: w.virtual_mcp_id ?? undefined,
|
|
259
267
|
steps: w.steps,
|
|
260
|
-
toolId: w.toolId ??
|
|
268
|
+
toolId: w.toolId ?? undefined,
|
|
269
|
+
inputSchema: w.inputSchema ?? undefined,
|
|
261
270
|
})),
|
|
262
271
|
);
|
|
263
272
|
}
|
|
@@ -383,6 +392,10 @@ async function doSyncWorkflows(
|
|
|
383
392
|
virtual_mcp_id: resolvedVmcpId,
|
|
384
393
|
}),
|
|
385
394
|
steps: wf.steps,
|
|
395
|
+
input_schema:
|
|
396
|
+
wf.inputSchema === undefined
|
|
397
|
+
? undefined
|
|
398
|
+
: (wf.inputSchema ?? null),
|
|
386
399
|
},
|
|
387
400
|
});
|
|
388
401
|
if (!result.success) {
|
|
@@ -402,6 +415,7 @@ async function doSyncWorkflows(
|
|
|
402
415
|
description: wf.description,
|
|
403
416
|
virtual_mcp_id: resolvedVmcpId,
|
|
404
417
|
steps: wf.steps,
|
|
418
|
+
input_schema: wf.inputSchema ?? null,
|
|
405
419
|
},
|
|
406
420
|
});
|
|
407
421
|
console.log(`${tag} CREATE "${wf.title}" OK`);
|
|
@@ -596,8 +610,30 @@ type InputForTool<
|
|
|
596
610
|
: StepInput<TSteps>
|
|
597
611
|
: StepInput<TSteps>;
|
|
598
612
|
|
|
599
|
-
|
|
600
|
-
|
|
613
|
+
/**
|
|
614
|
+
* Typed bail condition with @ref autocomplete.
|
|
615
|
+
* Self-references (e.g. `@thisStep.field` on step "thisStep") are valid for
|
|
616
|
+
* bail — the condition is evaluated after the step completes — but the current
|
|
617
|
+
* step name isn't in TSteps yet. Use the `(string & {})` escape hatch.
|
|
618
|
+
*/
|
|
619
|
+
type TypedBail<TSteps extends string> =
|
|
620
|
+
| true
|
|
621
|
+
| {
|
|
622
|
+
ref: KnownRefs<TSteps>;
|
|
623
|
+
eq?: unknown;
|
|
624
|
+
neq?: unknown;
|
|
625
|
+
gt?: number;
|
|
626
|
+
lt?: number;
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
type BaseStepFields<TSteps extends string> = Omit<
|
|
630
|
+
Step,
|
|
631
|
+
"name" | "input" | "action" | "bail"
|
|
632
|
+
> & { bail?: TypedBail<TSteps> };
|
|
633
|
+
type BaseForEachFields<TSteps extends string> = Omit<
|
|
634
|
+
Step,
|
|
635
|
+
"name" | "forEach" | "input" | "action" | "bail"
|
|
636
|
+
> & { bail?: TypedBail<TSteps> };
|
|
601
637
|
|
|
602
638
|
/**
|
|
603
639
|
* Tool-call variants of StepOpts — one discriminated member per tool ID so
|
|
@@ -608,12 +644,12 @@ type ToolCallStepOpts<
|
|
|
608
644
|
TSteps extends string,
|
|
609
645
|
TTools extends readonly ToolLike[],
|
|
610
646
|
> = [TTools[number]] extends [never]
|
|
611
|
-
? BaseStepFields & {
|
|
647
|
+
? BaseStepFields<TSteps> & {
|
|
612
648
|
action: { toolName: string & {}; transformCode?: string };
|
|
613
649
|
input?: StepInput<TSteps>;
|
|
614
650
|
}
|
|
615
651
|
: {
|
|
616
|
-
[TId in TTools[number]["id"]]: BaseStepFields & {
|
|
652
|
+
[TId in TTools[number]["id"]]: BaseStepFields<TSteps> & {
|
|
617
653
|
action: { toolName: TId; transformCode?: string };
|
|
618
654
|
input?: InputForTool<TTools, TId, TSteps>;
|
|
619
655
|
};
|
|
@@ -621,19 +657,22 @@ type ToolCallStepOpts<
|
|
|
621
657
|
|
|
622
658
|
type StepOpts<TSteps extends string, TTools extends readonly ToolLike[]> =
|
|
623
659
|
| ToolCallStepOpts<TSteps, TTools>
|
|
624
|
-
| (BaseStepFields & {
|
|
660
|
+
| (BaseStepFields<TSteps> & {
|
|
661
|
+
action: { code: string };
|
|
662
|
+
input?: StepInput<TSteps>;
|
|
663
|
+
});
|
|
625
664
|
|
|
626
665
|
type ToolCallForEachOpts<
|
|
627
666
|
TSteps extends string,
|
|
628
667
|
TTools extends readonly ToolLike[],
|
|
629
668
|
> = [TTools[number]] extends [never]
|
|
630
|
-
? BaseForEachFields & {
|
|
669
|
+
? BaseForEachFields<TSteps> & {
|
|
631
670
|
action: { toolName: string & {}; transformCode?: string };
|
|
632
671
|
input?: StepInput<TSteps>;
|
|
633
672
|
concurrency?: number;
|
|
634
673
|
}
|
|
635
674
|
: {
|
|
636
|
-
[TId in TTools[number]["id"]]: BaseForEachFields & {
|
|
675
|
+
[TId in TTools[number]["id"]]: BaseForEachFields<TSteps> & {
|
|
637
676
|
action: { toolName: TId; transformCode?: string };
|
|
638
677
|
input?: InputForTool<TTools, TId, TSteps>;
|
|
639
678
|
concurrency?: number;
|
|
@@ -645,7 +684,7 @@ type ForEachItemOpts<
|
|
|
645
684
|
TTools extends readonly ToolLike[],
|
|
646
685
|
> =
|
|
647
686
|
| ToolCallForEachOpts<TSteps, TTools>
|
|
648
|
-
| (BaseForEachFields & {
|
|
687
|
+
| (BaseForEachFields<TSteps> & {
|
|
649
688
|
action: { code: string };
|
|
650
689
|
input?: StepInput<TSteps>;
|
|
651
690
|
concurrency?: number;
|