@polka-codes/core 0.9.78 → 0.9.80
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/README.md +3 -3
- package/dist/_tsup-dts-rollup.d.ts +240 -20
- package/dist/index.d.ts +24 -3
- package/dist/index.js +939 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1904,6 +1904,935 @@ var agentWorkflow = async (input, { step, tools, logger }) => {
|
|
|
1904
1904
|
throw new Error("Maximum number of tool round trips reached.");
|
|
1905
1905
|
};
|
|
1906
1906
|
|
|
1907
|
+
// src/workflow/dynamic.ts
|
|
1908
|
+
import { parse } from "yaml";
|
|
1909
|
+
import { z as z22 } from "zod";
|
|
1910
|
+
|
|
1911
|
+
// src/workflow/dynamic-types.ts
|
|
1912
|
+
import { z as z21 } from "zod";
|
|
1913
|
+
var WorkflowInputDefinitionSchema = z21.object({
|
|
1914
|
+
id: z21.string(),
|
|
1915
|
+
description: z21.string().nullish(),
|
|
1916
|
+
default: z21.any().nullish()
|
|
1917
|
+
});
|
|
1918
|
+
var WorkflowStepDefinitionSchema = z21.object({
|
|
1919
|
+
id: z21.string(),
|
|
1920
|
+
tools: z21.array(z21.string()).nullish(),
|
|
1921
|
+
task: z21.string(),
|
|
1922
|
+
output: z21.string().nullish(),
|
|
1923
|
+
expected_outcome: z21.string().nullish(),
|
|
1924
|
+
/**
|
|
1925
|
+
* Persisted JavaScript/TypeScript (JS-compatible) async function body.
|
|
1926
|
+
* The code is wrapped as: `async (ctx) => { <code> }`.
|
|
1927
|
+
*/
|
|
1928
|
+
code: z21.string().nullish(),
|
|
1929
|
+
/**
|
|
1930
|
+
* Optional JSON schema or other metadata for future structured outputs.
|
|
1931
|
+
* Not interpreted by core today.
|
|
1932
|
+
*/
|
|
1933
|
+
outputSchema: z21.any().nullish(),
|
|
1934
|
+
/**
|
|
1935
|
+
* Optional timeout in milliseconds. Step execution will be aborted if it exceeds this duration.
|
|
1936
|
+
*/
|
|
1937
|
+
timeout: z21.number().positive().nullish()
|
|
1938
|
+
});
|
|
1939
|
+
var WorkflowDefinitionSchema = z21.object({
|
|
1940
|
+
task: z21.string(),
|
|
1941
|
+
inputs: z21.array(WorkflowInputDefinitionSchema).nullish(),
|
|
1942
|
+
steps: z21.array(WorkflowStepDefinitionSchema),
|
|
1943
|
+
output: z21.string().nullish()
|
|
1944
|
+
});
|
|
1945
|
+
var WorkflowFileSchema = z21.object({
|
|
1946
|
+
workflows: z21.record(z21.string(), WorkflowDefinitionSchema)
|
|
1947
|
+
});
|
|
1948
|
+
|
|
1949
|
+
// src/workflow/dynamic.ts
|
|
1950
|
+
function parseDynamicWorkflowDefinition(source) {
|
|
1951
|
+
try {
|
|
1952
|
+
const raw = parse(source);
|
|
1953
|
+
const validated = WorkflowFileSchema.safeParse(raw);
|
|
1954
|
+
if (!validated.success) {
|
|
1955
|
+
return { success: false, error: z22.prettifyError(validated.error) };
|
|
1956
|
+
}
|
|
1957
|
+
return { success: true, definition: validated.data };
|
|
1958
|
+
} catch (error) {
|
|
1959
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
var AsyncFunction = Object.getPrototypeOf(async () => {
|
|
1963
|
+
}).constructor;
|
|
1964
|
+
function validateAndApplyDefaults(workflowId, workflow, input) {
|
|
1965
|
+
if (!workflow.inputs || workflow.inputs.length === 0) {
|
|
1966
|
+
return input;
|
|
1967
|
+
}
|
|
1968
|
+
const validatedInput = {};
|
|
1969
|
+
const errors = [];
|
|
1970
|
+
for (const inputDef of workflow.inputs) {
|
|
1971
|
+
const providedValue = input[inputDef.id];
|
|
1972
|
+
if (providedValue !== void 0 && providedValue !== null) {
|
|
1973
|
+
validatedInput[inputDef.id] = providedValue;
|
|
1974
|
+
} else if (inputDef.default !== void 0 && inputDef.default !== null) {
|
|
1975
|
+
validatedInput[inputDef.id] = inputDef.default;
|
|
1976
|
+
} else {
|
|
1977
|
+
errors.push(`Missing required input '${inputDef.id}'${inputDef.description ? `: ${inputDef.description}` : ""}`);
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
if (errors.length > 0) {
|
|
1981
|
+
throw new Error(`Workflow '${workflowId}' input validation failed:
|
|
1982
|
+
${errors.map((e) => ` - ${e}`).join("\n")}`);
|
|
1983
|
+
}
|
|
1984
|
+
return validatedInput;
|
|
1985
|
+
}
|
|
1986
|
+
function createRunWorkflowFn(args) {
|
|
1987
|
+
return async (subWorkflowId, subInput) => {
|
|
1988
|
+
const mergedInput = { ...args.input, ...args.state, ...subInput ?? {} };
|
|
1989
|
+
return await args.runInternal(subWorkflowId, mergedInput, args.context, args.state);
|
|
1990
|
+
};
|
|
1991
|
+
}
|
|
1992
|
+
function compileStep(stepDef, workflowId, compiledSteps) {
|
|
1993
|
+
const key = `${workflowId}.${stepDef.id}`;
|
|
1994
|
+
const existing = compiledSteps.get(key);
|
|
1995
|
+
if (existing) {
|
|
1996
|
+
return existing;
|
|
1997
|
+
}
|
|
1998
|
+
if (!stepDef.code) {
|
|
1999
|
+
throw new Error(`Step '${stepDef.id}' in workflow '${workflowId}' has no code`);
|
|
2000
|
+
}
|
|
2001
|
+
try {
|
|
2002
|
+
const fn = new AsyncFunction("ctx", stepDef.code);
|
|
2003
|
+
compiledSteps.set(key, fn);
|
|
2004
|
+
return fn;
|
|
2005
|
+
} catch (error) {
|
|
2006
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
2007
|
+
const codePreview = stepDef.code.length > 200 ? `${stepDef.code.substring(0, 200)}...` : stepDef.code;
|
|
2008
|
+
throw new Error(
|
|
2009
|
+
`Failed to compile code for step '${stepDef.id}' in workflow '${workflowId}':
|
|
2010
|
+
Error: ${errorMsg}
|
|
2011
|
+
Code:
|
|
2012
|
+
${codePreview.split("\n").map((line) => ` ${line}`).join("\n")}`
|
|
2013
|
+
);
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
async function executeStepWithAgent(stepDef, workflowId, input, state, context, options, runInternal) {
|
|
2017
|
+
const tools = context.tools;
|
|
2018
|
+
if (typeof tools.generateText !== "function" || typeof tools.invokeTool !== "function" || typeof tools.taskEvent !== "function") {
|
|
2019
|
+
throw new Error(
|
|
2020
|
+
`Step '${stepDef.id}' in workflow '${workflowId}' requires agent execution, but AgentToolRegistry tools are not available.`
|
|
2021
|
+
);
|
|
2022
|
+
}
|
|
2023
|
+
if (!options.toolInfo) {
|
|
2024
|
+
throw new Error(
|
|
2025
|
+
`Step '${stepDef.id}' in workflow '${workflowId}' requires agent execution, but no toolInfo was provided to DynamicWorkflowRunner.`
|
|
2026
|
+
);
|
|
2027
|
+
}
|
|
2028
|
+
const allowedToolNames = stepDef.tools;
|
|
2029
|
+
const toolsForAgent = allowedToolNames ? options.toolInfo.filter((t) => allowedToolNames.includes(t.name)) : [...options.toolInfo];
|
|
2030
|
+
if (!allowedToolNames || allowedToolNames.includes("runWorkflow")) {
|
|
2031
|
+
toolsForAgent.push({
|
|
2032
|
+
name: "runWorkflow",
|
|
2033
|
+
description: "Run a named sub-workflow defined in the current workflow file.",
|
|
2034
|
+
parameters: z22.object({
|
|
2035
|
+
workflowId: z22.string().describe("Sub-workflow id to run"),
|
|
2036
|
+
input: z22.any().nullish().describe("Optional input object for the sub-workflow")
|
|
2037
|
+
}),
|
|
2038
|
+
handler: async () => {
|
|
2039
|
+
return { type: "Error" /* Error */, message: { type: "error-text", value: "runWorkflow is virtual." } };
|
|
2040
|
+
}
|
|
2041
|
+
});
|
|
2042
|
+
}
|
|
2043
|
+
const allowedToolNameSet = new Set(toolsForAgent.map((t) => t.name));
|
|
2044
|
+
context.logger.debug(`[Agent] Available tools for step '${stepDef.id}': ${toolsForAgent.map((t) => t.name).join(", ")}`);
|
|
2045
|
+
const systemPrompt = options.stepSystemPrompt?.({ workflowId, step: stepDef, input, state }) ?? [
|
|
2046
|
+
`You are an AI assistant executing a workflow step.`,
|
|
2047
|
+
"",
|
|
2048
|
+
"# Instructions",
|
|
2049
|
+
"- Execute the task defined in the user message.",
|
|
2050
|
+
"- Use the provided tools to accomplish the task.",
|
|
2051
|
+
"- Return the step output as valid JSON in markdown.",
|
|
2052
|
+
"- Do not ask for user input. If information is missing, make a reasonable assumption or fail."
|
|
2053
|
+
].filter(Boolean).join("\n");
|
|
2054
|
+
const userContent = [
|
|
2055
|
+
`Workflow: ${workflowId}`,
|
|
2056
|
+
`Step: ${stepDef.id}`,
|
|
2057
|
+
`Task: ${stepDef.task}`,
|
|
2058
|
+
stepDef.expected_outcome ? `Expected outcome: ${stepDef.expected_outcome}` : "",
|
|
2059
|
+
`Workflow Input: ${JSON.stringify(input)}`,
|
|
2060
|
+
`Current State: ${JSON.stringify(state)}`
|
|
2061
|
+
].filter(Boolean).join("\n");
|
|
2062
|
+
const runWorkflow = createRunWorkflowFn({ input, state, context, runInternal });
|
|
2063
|
+
const agentTools = {
|
|
2064
|
+
generateText: tools.generateText.bind(tools),
|
|
2065
|
+
taskEvent: tools.taskEvent.bind(tools),
|
|
2066
|
+
invokeTool: async ({ toolName, input: toolInput }) => {
|
|
2067
|
+
if (!allowedToolNameSet.has(toolName)) {
|
|
2068
|
+
return {
|
|
2069
|
+
type: "Error" /* Error */,
|
|
2070
|
+
message: { type: "error-text", value: `Tool '${toolName}' is not allowed in this step.` }
|
|
2071
|
+
};
|
|
2072
|
+
}
|
|
2073
|
+
if (toolName === "runWorkflow") {
|
|
2074
|
+
const subWorkflowId = toolInput?.workflowId;
|
|
2075
|
+
const subInput = toolInput?.input;
|
|
2076
|
+
if (typeof subWorkflowId !== "string") {
|
|
2077
|
+
return {
|
|
2078
|
+
type: "Error" /* Error */,
|
|
2079
|
+
message: { type: "error-text", value: "runWorkflow.workflowId must be a string." }
|
|
2080
|
+
};
|
|
2081
|
+
}
|
|
2082
|
+
try {
|
|
2083
|
+
const output = await runWorkflow(subWorkflowId, subInput);
|
|
2084
|
+
const jsonResult = { type: "json", value: output };
|
|
2085
|
+
return { type: "Reply" /* Reply */, message: jsonResult };
|
|
2086
|
+
} catch (error) {
|
|
2087
|
+
return {
|
|
2088
|
+
type: "Error" /* Error */,
|
|
2089
|
+
message: { type: "error-text", value: error instanceof Error ? error.message : String(error) }
|
|
2090
|
+
};
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
return await tools.invokeTool({ toolName, input: toolInput });
|
|
2094
|
+
}
|
|
2095
|
+
};
|
|
2096
|
+
const result = await agentWorkflow(
|
|
2097
|
+
{
|
|
2098
|
+
tools: toolsForAgent,
|
|
2099
|
+
systemPrompt,
|
|
2100
|
+
userMessage: [{ role: "user", content: userContent }],
|
|
2101
|
+
maxToolRoundTrips: options.maxToolRoundTrips,
|
|
2102
|
+
model: options.model
|
|
2103
|
+
},
|
|
2104
|
+
{ ...context, tools: agentTools }
|
|
2105
|
+
);
|
|
2106
|
+
if (result.type === "Exit") {
|
|
2107
|
+
if (result.object !== void 0) {
|
|
2108
|
+
return result.object;
|
|
2109
|
+
}
|
|
2110
|
+
const parsed = parseJsonFromMarkdown(result.message);
|
|
2111
|
+
if (parsed.success) {
|
|
2112
|
+
return parsed.data;
|
|
2113
|
+
}
|
|
2114
|
+
if (options.wrapAgentResultInObject) {
|
|
2115
|
+
context.logger.warn(`[Agent] Step '${stepDef.id}' returned plain text instead of JSON. Wrapping in {result: ...}`);
|
|
2116
|
+
return { result: result.message };
|
|
2117
|
+
}
|
|
2118
|
+
return result.message;
|
|
2119
|
+
}
|
|
2120
|
+
if (result.type === "Error") {
|
|
2121
|
+
throw new Error(`Agent step '${stepDef.id}' in workflow '${workflowId}' failed: ${result.error?.message || "Unknown error"}`);
|
|
2122
|
+
}
|
|
2123
|
+
if (result.type === "UsageExceeded") {
|
|
2124
|
+
throw new Error(`Agent step '${stepDef.id}' in workflow '${workflowId}' exceeded usage limits (tokens or rounds)`);
|
|
2125
|
+
}
|
|
2126
|
+
throw new Error(`Agent step '${stepDef.id}' in workflow '${workflowId}' exited unexpectedly with type: ${result.type}`);
|
|
2127
|
+
}
|
|
2128
|
+
async function executeStepWithTimeout(stepDef, workflowId, input, state, context, options, compiledSteps, runInternal) {
|
|
2129
|
+
const executeStepLogic = async () => {
|
|
2130
|
+
if (stepDef.code && options.allowUnsafeCodeExecution) {
|
|
2131
|
+
context.logger.debug(`[Step] Executing step '${stepDef.id}' with compiled code`);
|
|
2132
|
+
const fn = compileStep(stepDef, workflowId, compiledSteps);
|
|
2133
|
+
const runWorkflow = createRunWorkflowFn({ input, state, context, runInternal });
|
|
2134
|
+
const runtimeCtx = {
|
|
2135
|
+
workflowId,
|
|
2136
|
+
stepId: stepDef.id,
|
|
2137
|
+
input,
|
|
2138
|
+
state,
|
|
2139
|
+
tools: context.tools,
|
|
2140
|
+
logger: context.logger,
|
|
2141
|
+
step: context.step,
|
|
2142
|
+
runWorkflow,
|
|
2143
|
+
toolInfo: options.toolInfo
|
|
2144
|
+
};
|
|
2145
|
+
const result2 = await fn(runtimeCtx);
|
|
2146
|
+
context.logger.debug(`[Step] Compiled code execution completed for step '${stepDef.id}'`);
|
|
2147
|
+
return result2;
|
|
2148
|
+
}
|
|
2149
|
+
context.logger.debug(`[Step] Executing step '${stepDef.id}' with agent`);
|
|
2150
|
+
const result = await executeStepWithAgent(stepDef, workflowId, input, state, context, options, runInternal);
|
|
2151
|
+
context.logger.debug(`[Step] Agent execution completed for step '${stepDef.id}'`);
|
|
2152
|
+
return result;
|
|
2153
|
+
};
|
|
2154
|
+
if (stepDef.timeout && stepDef.timeout > 0) {
|
|
2155
|
+
context.logger.debug(`[Step] Step '${stepDef.id}' has timeout of ${stepDef.timeout}ms`);
|
|
2156
|
+
let timeoutId;
|
|
2157
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
2158
|
+
timeoutId = setTimeout(
|
|
2159
|
+
() => reject(new Error(`Step '${stepDef.id}' in workflow '${workflowId}' timed out after ${stepDef.timeout}ms`)),
|
|
2160
|
+
stepDef.timeout
|
|
2161
|
+
);
|
|
2162
|
+
});
|
|
2163
|
+
try {
|
|
2164
|
+
return await Promise.race([executeStepLogic(), timeoutPromise]);
|
|
2165
|
+
} finally {
|
|
2166
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
return await executeStepLogic();
|
|
2170
|
+
}
|
|
2171
|
+
async function executeStep(stepDef, workflowId, input, state, context, options, compiledSteps, runInternal) {
|
|
2172
|
+
const result = await executeStepWithTimeout(stepDef, workflowId, input, state, context, options, compiledSteps, runInternal);
|
|
2173
|
+
if (stepDef.outputSchema) {
|
|
2174
|
+
try {
|
|
2175
|
+
const _schema = z22.any();
|
|
2176
|
+
if (typeof stepDef.outputSchema === "object") {
|
|
2177
|
+
context.logger.debug(`[Step] Validating output for step '${stepDef.id}' against schema`);
|
|
2178
|
+
if (stepDef.outputSchema.type === "object") {
|
|
2179
|
+
if (typeof result !== "object" || result === null || Array.isArray(result)) {
|
|
2180
|
+
throw new Error(`Expected object output, got ${Array.isArray(result) ? "array" : result === null ? "null" : typeof result}`);
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
if (stepDef.outputSchema.type === "array" && !Array.isArray(result)) {
|
|
2184
|
+
throw new Error(`Expected array output, got ${typeof result}`);
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
} catch (error) {
|
|
2188
|
+
throw new Error(
|
|
2189
|
+
`Step '${stepDef.id}' in workflow '${workflowId}' output validation failed: ${error instanceof Error ? error.message : String(error)}`
|
|
2190
|
+
);
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
return result;
|
|
2194
|
+
}
|
|
2195
|
+
function createDynamicWorkflow(definition, options = {}) {
|
|
2196
|
+
if (typeof definition === "string") {
|
|
2197
|
+
const res = parseDynamicWorkflowDefinition(definition);
|
|
2198
|
+
if (!res.success) {
|
|
2199
|
+
throw new Error(res.error);
|
|
2200
|
+
}
|
|
2201
|
+
definition = res.definition;
|
|
2202
|
+
}
|
|
2203
|
+
const compiledSteps = /* @__PURE__ */ new Map();
|
|
2204
|
+
const runInternal = async (workflowId, input, context, inheritedState) => {
|
|
2205
|
+
const workflow = definition.workflows[workflowId];
|
|
2206
|
+
if (!workflow) {
|
|
2207
|
+
throw new Error(`Workflow '${workflowId}' not found`);
|
|
2208
|
+
}
|
|
2209
|
+
const validatedInput = validateAndApplyDefaults(workflowId, workflow, input);
|
|
2210
|
+
context.logger.info(`[Workflow] Starting workflow '${workflowId}'`);
|
|
2211
|
+
context.logger.debug(`[Workflow] Input: ${JSON.stringify(validatedInput)}`);
|
|
2212
|
+
context.logger.debug(`[Workflow] Inherited state: ${JSON.stringify(inheritedState)}`);
|
|
2213
|
+
context.logger.debug(`[Workflow] Steps: ${workflow.steps.map((s) => s.id).join(", ")}`);
|
|
2214
|
+
const state = { ...inheritedState };
|
|
2215
|
+
let lastOutput;
|
|
2216
|
+
for (let i = 0; i < workflow.steps.length; i++) {
|
|
2217
|
+
const stepDef = workflow.steps[i];
|
|
2218
|
+
const stepName = `${workflowId}.${stepDef.id}`;
|
|
2219
|
+
context.logger.info(`[Workflow] Step ${i + 1}/${workflow.steps.length}: ${stepDef.id}`);
|
|
2220
|
+
context.logger.debug(`[Workflow] Step task: ${stepDef.task}`);
|
|
2221
|
+
if (stepDef.expected_outcome) {
|
|
2222
|
+
context.logger.debug(`[Workflow] Expected outcome: ${stepDef.expected_outcome}`);
|
|
2223
|
+
}
|
|
2224
|
+
context.logger.debug(`[Workflow] Current state keys: ${Object.keys(state).join(", ")}`);
|
|
2225
|
+
lastOutput = await context.step(stepName, async () => {
|
|
2226
|
+
return await executeStep(stepDef, workflowId, validatedInput, state, context, options, compiledSteps, runInternal);
|
|
2227
|
+
});
|
|
2228
|
+
const outputKey = stepDef.output ?? stepDef.id;
|
|
2229
|
+
state[outputKey] = lastOutput;
|
|
2230
|
+
context.logger.debug(
|
|
2231
|
+
`[Workflow] Step output stored as '${outputKey}': ${typeof lastOutput === "object" ? JSON.stringify(lastOutput).substring(0, 200) : lastOutput}`
|
|
2232
|
+
);
|
|
2233
|
+
}
|
|
2234
|
+
context.logger.info(`[Workflow] Completed workflow '${workflowId}'`);
|
|
2235
|
+
if (workflow.output) {
|
|
2236
|
+
context.logger.debug(`[Workflow] Returning output field: ${workflow.output}`);
|
|
2237
|
+
return state[workflow.output];
|
|
2238
|
+
}
|
|
2239
|
+
context.logger.debug(`[Workflow] Returning full state with keys: ${Object.keys(state).join(", ")}`);
|
|
2240
|
+
return state;
|
|
2241
|
+
};
|
|
2242
|
+
return async (workflowId, input, context) => {
|
|
2243
|
+
return await runInternal(workflowId, input, context, {});
|
|
2244
|
+
};
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
// src/workflow/dynamic-generator.workflow.ts
|
|
2248
|
+
import { z as z23 } from "zod";
|
|
2249
|
+
var GenerateWorkflowDefinitionInputSchema = z23.object({
|
|
2250
|
+
prompt: z23.string(),
|
|
2251
|
+
availableTools: z23.array(
|
|
2252
|
+
z23.object({
|
|
2253
|
+
name: z23.string(),
|
|
2254
|
+
description: z23.string()
|
|
2255
|
+
})
|
|
2256
|
+
).optional()
|
|
2257
|
+
});
|
|
2258
|
+
var GenerateWorkflowCodeInputSchema = z23.object({
|
|
2259
|
+
workflow: WorkflowFileSchema
|
|
2260
|
+
});
|
|
2261
|
+
var WORKFLOW_DEFINITION_SYSTEM_PROMPT = `You are an expert workflow architect.
|
|
2262
|
+
Your task is to create a JSON workflow definition based on the user's request.
|
|
2263
|
+
|
|
2264
|
+
The workflow definition must follow this structure:
|
|
2265
|
+
{
|
|
2266
|
+
"workflows": {
|
|
2267
|
+
"workflowName": {
|
|
2268
|
+
"task": "Description of the workflow",
|
|
2269
|
+
"inputs": [
|
|
2270
|
+
{ "id": "inputName", "description": "Description", "default": "optionalDefault" }
|
|
2271
|
+
],
|
|
2272
|
+
"steps": [
|
|
2273
|
+
{
|
|
2274
|
+
"id": "stepId",
|
|
2275
|
+
"task": "Description of the step",
|
|
2276
|
+
"tools": ["toolName1", "toolName2"], // Optional: restrict which tools can be used
|
|
2277
|
+
"output": "outputVariableName", // Optional: defaults to step id
|
|
2278
|
+
"timeout": 30000, // Optional: timeout in milliseconds
|
|
2279
|
+
"expected_outcome": "What this step produces", // Optional: documentation
|
|
2280
|
+
"outputSchema": { "type": "object" } // Optional: validation schema
|
|
2281
|
+
}
|
|
2282
|
+
],
|
|
2283
|
+
"output": "outputVariableName" // Optional
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
Constraints:
|
|
2289
|
+
- You MUST always include a workflow named 'main'. This is the entry point.
|
|
2290
|
+
- The 'main' workflow input must be either empty (no input) or a single string input.
|
|
2291
|
+
- Break down complex tasks into logical steps.
|
|
2292
|
+
- Define clear inputs and outputs.
|
|
2293
|
+
|
|
2294
|
+
Quality Guidelines:
|
|
2295
|
+
- Add "timeout" field (in milliseconds) for steps that might take long (file I/O, API calls, searches)
|
|
2296
|
+
- Use "expected_outcome" field to document what each step should produce
|
|
2297
|
+
- Use descriptive step IDs (e.g., "validateInput", "fetchUserData", not "step1", "step2")
|
|
2298
|
+
- Design steps to be focused - one responsibility per step
|
|
2299
|
+
- For steps that process multiple items, consider creating a sub-workflow
|
|
2300
|
+
- Add "outputSchema" with type information for validation-critical steps
|
|
2301
|
+
- Order steps logically with clear data flow
|
|
2302
|
+
|
|
2303
|
+
Example 1:
|
|
2304
|
+
User: "Research a topic and summarize it."
|
|
2305
|
+
Output:
|
|
2306
|
+
\`\`\`json
|
|
2307
|
+
{
|
|
2308
|
+
"workflows": {
|
|
2309
|
+
"main": {
|
|
2310
|
+
"task": "Research a topic and provide a summary",
|
|
2311
|
+
"inputs": [
|
|
2312
|
+
{ "id": "topic", "description": "The topic to research" }
|
|
2313
|
+
],
|
|
2314
|
+
"steps": [
|
|
2315
|
+
{
|
|
2316
|
+
"id": "search",
|
|
2317
|
+
"task": "Search for information about the topic",
|
|
2318
|
+
"tools": ["search"],
|
|
2319
|
+
"output": "searchResults"
|
|
2320
|
+
},
|
|
2321
|
+
{
|
|
2322
|
+
"id": "summarize",
|
|
2323
|
+
"task": "Summarize the search results",
|
|
2324
|
+
"tools": ["generateText"],
|
|
2325
|
+
"output": "summary"
|
|
2326
|
+
}
|
|
2327
|
+
],
|
|
2328
|
+
"output": "summary"
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
\`\`\`
|
|
2333
|
+
|
|
2334
|
+
Example 2:
|
|
2335
|
+
User: "Review urgent PRs. For each PR, run the review workflow."
|
|
2336
|
+
Output:
|
|
2337
|
+
\`\`\`json
|
|
2338
|
+
{
|
|
2339
|
+
"workflows": {
|
|
2340
|
+
"main": {
|
|
2341
|
+
"task": "Fetch urgent PRs and review them",
|
|
2342
|
+
"inputs": [],
|
|
2343
|
+
"steps": [
|
|
2344
|
+
{
|
|
2345
|
+
"id": "fetchPRs",
|
|
2346
|
+
"task": "Fetch list of urgent PRs",
|
|
2347
|
+
"tools": ["github_list_prs"],
|
|
2348
|
+
"output": "prs"
|
|
2349
|
+
},
|
|
2350
|
+
{
|
|
2351
|
+
"id": "reviewEachPR",
|
|
2352
|
+
"task": "Run review workflow for each PR",
|
|
2353
|
+
"tools": [],
|
|
2354
|
+
"output": "reviews"
|
|
2355
|
+
}
|
|
2356
|
+
],
|
|
2357
|
+
"output": "reviews"
|
|
2358
|
+
},
|
|
2359
|
+
"reviewPR": {
|
|
2360
|
+
"task": "Review a single PR",
|
|
2361
|
+
"inputs": [
|
|
2362
|
+
{ "id": "prId", "description": "ID of the PR to review" }
|
|
2363
|
+
],
|
|
2364
|
+
"steps": [
|
|
2365
|
+
{
|
|
2366
|
+
"id": "getDiff",
|
|
2367
|
+
"task": "Get PR diff",
|
|
2368
|
+
"tools": ["github_get_diff"],
|
|
2369
|
+
"output": "diff"
|
|
2370
|
+
},
|
|
2371
|
+
{
|
|
2372
|
+
"id": "analyze",
|
|
2373
|
+
"task": "Analyze the diff",
|
|
2374
|
+
"tools": ["generateText"],
|
|
2375
|
+
"output": "analysis"
|
|
2376
|
+
}
|
|
2377
|
+
],
|
|
2378
|
+
"output": "analysis"
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
\`\`\`
|
|
2383
|
+
`;
|
|
2384
|
+
var WORKFLOW_CODE_SYSTEM_PROMPT = `You are an expert TypeScript developer.
|
|
2385
|
+
Your task is to implement the TypeScript code for the steps in the provided workflow definition.
|
|
2386
|
+
|
|
2387
|
+
You will receive a JSON workflow definition where the "code" field is null.
|
|
2388
|
+
You must fill in the "code" field for each step with valid TypeScript code.
|
|
2389
|
+
|
|
2390
|
+
CRITICAL: Each step "code" field must contain ONLY the function body statements (the code inside the curly braces).
|
|
2391
|
+
DO NOT include function declaration, arrow function syntax, async keyword, parameter list, or outer curly braces.
|
|
2392
|
+
|
|
2393
|
+
The code will be wrapped automatically in: \`async (ctx) => { YOUR_CODE_HERE }\`
|
|
2394
|
+
|
|
2395
|
+
Example of CORRECT code field:
|
|
2396
|
+
\`\`\`ts
|
|
2397
|
+
const result = await ctx.tools.readFile({ path: 'README.md' })
|
|
2398
|
+
if (!result) throw new Error('File not found')
|
|
2399
|
+
return result
|
|
2400
|
+
\`\`\`
|
|
2401
|
+
|
|
2402
|
+
Example of INCORRECT code field (DO NOT DO THIS):
|
|
2403
|
+
\`\`\`ts
|
|
2404
|
+
async (ctx) => {
|
|
2405
|
+
const result = await ctx.tools.readFile({ path: 'README.md' })
|
|
2406
|
+
return result
|
|
2407
|
+
}
|
|
2408
|
+
\`\`\`
|
|
2409
|
+
|
|
2410
|
+
Example of INCORRECT code field (DO NOT DO THIS):
|
|
2411
|
+
\`\`\`ts
|
|
2412
|
+
(ctx) => {
|
|
2413
|
+
return 'hello'
|
|
2414
|
+
}
|
|
2415
|
+
\`\`\`
|
|
2416
|
+
|
|
2417
|
+
## Runtime context (ctx)
|
|
2418
|
+
\`\`\`ts
|
|
2419
|
+
// Runtime types (for reference)
|
|
2420
|
+
type Logger = {
|
|
2421
|
+
debug: (...args: any[]) => void
|
|
2422
|
+
info: (...args: any[]) => void
|
|
2423
|
+
warn: (...args: any[]) => void
|
|
2424
|
+
error: (...args: any[]) => void
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
type StepFn = {
|
|
2428
|
+
<T>(name: string, fn: () => Promise<T>): Promise<T>
|
|
2429
|
+
<T>(name: string, options: { retry?: number }, fn: () => Promise<T>): Promise<T>
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
type JsonModelMessage = { role: 'system' | 'user' | 'assistant' | 'tool'; content: any }
|
|
2433
|
+
type JsonResponseMessage = { role: 'assistant' | 'tool'; content: any }
|
|
2434
|
+
type ToolSet = Record<string, any>
|
|
2435
|
+
|
|
2436
|
+
type ToolResponseResult =
|
|
2437
|
+
| { type: 'text'; value: string }
|
|
2438
|
+
| { type: 'json'; value: any }
|
|
2439
|
+
| { type: 'error-text'; value: string }
|
|
2440
|
+
| { type: 'error-json'; value: any }
|
|
2441
|
+
| { type: 'content'; value: any[] }
|
|
2442
|
+
|
|
2443
|
+
type AgentToolResponse =
|
|
2444
|
+
| { type: 'Reply'; message: ToolResponseResult }
|
|
2445
|
+
| { type: 'Exit'; message: string; object?: any }
|
|
2446
|
+
| { type: 'Error'; message: ToolResponseResult }
|
|
2447
|
+
|
|
2448
|
+
type ExitReason =
|
|
2449
|
+
| { type: 'UsageExceeded' }
|
|
2450
|
+
| { type: 'Exit'; message: string; object?: any }
|
|
2451
|
+
| { type: 'Error'; error: { message: string; stack?: string } }
|
|
2452
|
+
|
|
2453
|
+
type FullAgentToolInfo = { name: string; description: string; parameters: any; handler: any }
|
|
2454
|
+
|
|
2455
|
+
// Tools available on ctx.tools in dynamic steps
|
|
2456
|
+
type DynamicWorkflowTools = {
|
|
2457
|
+
// LLM + agent helpers
|
|
2458
|
+
generateText: (input: { messages: JsonModelMessage[]; tools: ToolSet }) => Promise<JsonResponseMessage[]>
|
|
2459
|
+
runAgent: (input: {
|
|
2460
|
+
tools: Readonly<FullAgentToolInfo[]>
|
|
2461
|
+
maxToolRoundTrips?: number
|
|
2462
|
+
userMessage: readonly JsonModelMessage[]
|
|
2463
|
+
} & ({ messages: JsonModelMessage[] } | { systemPrompt: string })) => Promise<ExitReason>
|
|
2464
|
+
|
|
2465
|
+
// Generic bridge to "agent tools" by name
|
|
2466
|
+
invokeTool: (input: { toolName: string; input: any }) => Promise<AgentToolResponse>
|
|
2467
|
+
|
|
2468
|
+
// File + command helpers (direct)
|
|
2469
|
+
readFile: (input: { path: string }) => Promise<string | null>
|
|
2470
|
+
writeToFile: (input: { path: string; content: string }) => Promise<void>
|
|
2471
|
+
executeCommand: (input: { command: string; pipe?: boolean } & ({ args: string[]; shell?: false } | { shell: true })) => Promise<{
|
|
2472
|
+
exitCode: number
|
|
2473
|
+
stdout: string
|
|
2474
|
+
stderr: string
|
|
2475
|
+
}>
|
|
2476
|
+
|
|
2477
|
+
// CLI UX helpers
|
|
2478
|
+
confirm: (input: { message: string }) => Promise<boolean>
|
|
2479
|
+
input: (input: { message: string; default?: string }) => Promise<string>
|
|
2480
|
+
select: (input: { message: string; choices: { name: string; value: string }[] }) => Promise<string>
|
|
2481
|
+
}
|
|
2482
|
+
|
|
2483
|
+
type DynamicStepRuntimeContext = {
|
|
2484
|
+
workflowId: string
|
|
2485
|
+
stepId: string
|
|
2486
|
+
input: Record<string, any>
|
|
2487
|
+
state: Record<string, any>
|
|
2488
|
+
tools: DynamicWorkflowTools
|
|
2489
|
+
logger: Logger
|
|
2490
|
+
step: StepFn
|
|
2491
|
+
runWorkflow: (workflowId: string, input?: Record<string, any>) => Promise<any>
|
|
2492
|
+
toolInfo?: ReadonlyArray<FullAgentToolInfo>
|
|
2493
|
+
}
|
|
2494
|
+
\`\`\`
|
|
2495
|
+
|
|
2496
|
+
- \`ctx.input\`: workflow inputs (read-only).
|
|
2497
|
+
- \`ctx.state\`: shared state between steps (previous step outputs are stored here).
|
|
2498
|
+
- \`ctx.tools\`: async tool functions. Call tools as \`await ctx.tools.someTool({ ... })\`.
|
|
2499
|
+
- \`ctx.runWorkflow\`: run a sub-workflow by id.
|
|
2500
|
+
|
|
2501
|
+
## Guidelines
|
|
2502
|
+
- Use \`await\` for all async operations.
|
|
2503
|
+
- Return the output value for the step (this becomes the step output).
|
|
2504
|
+
- Access inputs via \`ctx.input.<inputId>\`.
|
|
2505
|
+
- Access previous step outputs via \`ctx.state.<stepOutputKey>\` (defaults to the step \`output\` or \`id\`).
|
|
2506
|
+
|
|
2507
|
+
## Quality Guidelines for Code Implementation
|
|
2508
|
+
|
|
2509
|
+
### Error Handling
|
|
2510
|
+
- ALWAYS validate inputs at the start of steps
|
|
2511
|
+
- Use try-catch for operations that might fail (file I/O, parsing, API calls)
|
|
2512
|
+
- Preserve stack traces: re-throw original errors rather than creating new ones
|
|
2513
|
+
- Use error type guards: \`const err = error instanceof Error ? error : new Error(String(error))\`
|
|
2514
|
+
- Check for null/undefined before using values
|
|
2515
|
+
- Handle edge cases (empty arrays, missing files, invalid data)
|
|
2516
|
+
|
|
2517
|
+
### Logging
|
|
2518
|
+
- Use \`ctx.logger.info()\` for important progress updates
|
|
2519
|
+
- Use \`ctx.logger.debug()\` for detailed information
|
|
2520
|
+
- Use \`ctx.logger.warn()\` for recoverable issues
|
|
2521
|
+
- Use \`ctx.logger.error()\` before throwing errors
|
|
2522
|
+
- Log when starting and completing significant operations
|
|
2523
|
+
- Use template literals for readability: \`ctx.logger.info(\\\`Processing \${items.length} items...\\\`)\`
|
|
2524
|
+
|
|
2525
|
+
### User Experience
|
|
2526
|
+
- Provide progress feedback for long operations
|
|
2527
|
+
- Return structured data (objects/arrays), not strings when possible
|
|
2528
|
+
- Include helpful metadata in results (counts, timestamps, status)
|
|
2529
|
+
- For batch operations, report progress: \`Processed 5/10 items\`
|
|
2530
|
+
|
|
2531
|
+
### Data Validation
|
|
2532
|
+
- Validate required fields exist before accessing
|
|
2533
|
+
- Check data types match expectations
|
|
2534
|
+
- Validate array lengths before iteration
|
|
2535
|
+
- Example: \`if (!data?.users || !Array.isArray(data.users)) throw new Error('Invalid data format')\`
|
|
2536
|
+
|
|
2537
|
+
### Best Practices
|
|
2538
|
+
- Use meaningful variable names
|
|
2539
|
+
- Avoid nested callbacks - use async/await
|
|
2540
|
+
- Clean up resources (close files, clear timeouts)
|
|
2541
|
+
- Return consistent data structures across similar steps
|
|
2542
|
+
- For iteration, consider batching or rate limiting
|
|
2543
|
+
|
|
2544
|
+
### When to Simplify
|
|
2545
|
+
- Simple transformation steps (e.g., formatting strings) need only basic error handling
|
|
2546
|
+
- Internal sub-workflow steps with validated inputs from parent can skip redundant validation
|
|
2547
|
+
- Minimal logging is fine for fast steps (<100ms) that don't perform I/O or external calls
|
|
2548
|
+
- Use judgment: match error handling complexity to the step's failure risk and impact
|
|
2549
|
+
|
|
2550
|
+
## Tool calling examples (every tool)
|
|
2551
|
+
|
|
2552
|
+
### Direct ctx.tools methods
|
|
2553
|
+
\`\`\`ts
|
|
2554
|
+
// readFile
|
|
2555
|
+
const readme = await ctx.tools.readFile({ path: 'README.md' })
|
|
2556
|
+
if (readme == null) throw new Error('README.md not found')
|
|
2557
|
+
|
|
2558
|
+
// writeToFile
|
|
2559
|
+
await ctx.tools.writeToFile({ path: 'notes.txt', content: 'hello\\n' })
|
|
2560
|
+
|
|
2561
|
+
// executeCommand (args form)
|
|
2562
|
+
const rg = await ctx.tools.executeCommand({ command: 'rg', args: ['-n', 'TODO', '.'] })
|
|
2563
|
+
if (rg.exitCode !== 0) throw new Error(rg.stderr)
|
|
2564
|
+
|
|
2565
|
+
// executeCommand (shell form)
|
|
2566
|
+
await ctx.tools.executeCommand({ command: 'ls -la', shell: true, pipe: true })
|
|
2567
|
+
|
|
2568
|
+
// generateText (LLM call; pass tools: {})
|
|
2569
|
+
const msgs = await ctx.tools.generateText({
|
|
2570
|
+
messages: [
|
|
2571
|
+
{ role: 'system', content: 'Summarize the following text.' },
|
|
2572
|
+
{ role: 'user', content: readme },
|
|
2573
|
+
],
|
|
2574
|
+
tools: {},
|
|
2575
|
+
})
|
|
2576
|
+
const last = msgs[msgs.length - 1]
|
|
2577
|
+
const lastText = typeof last?.content === 'string' ? last.content : JSON.stringify(last?.content)
|
|
2578
|
+
|
|
2579
|
+
// runAgent (nested agent; use ctx.toolInfo as the tool list)
|
|
2580
|
+
const agentRes = await ctx.tools.runAgent({
|
|
2581
|
+
systemPrompt: 'You are a helpful assistant.',
|
|
2582
|
+
userMessage: [{ role: 'user', content: 'Summarize README.md in 3 bullets.' }],
|
|
2583
|
+
tools: (ctx.toolInfo ?? []) as any,
|
|
2584
|
+
})
|
|
2585
|
+
if (agentRes.type !== 'Exit') throw new Error('runAgent failed')
|
|
2586
|
+
|
|
2587
|
+
// confirm / input / select (interactive)
|
|
2588
|
+
const ok = await ctx.tools.confirm({ message: 'Proceed?' })
|
|
2589
|
+
const name = await ctx.tools.input({ message: 'Name?', default: 'main' })
|
|
2590
|
+
const flavor = await ctx.tools.select({
|
|
2591
|
+
message: 'Pick one',
|
|
2592
|
+
choices: [
|
|
2593
|
+
{ name: 'A', value: 'a' },
|
|
2594
|
+
{ name: 'B', value: 'b' },
|
|
2595
|
+
],
|
|
2596
|
+
})
|
|
2597
|
+
|
|
2598
|
+
\`\`\`
|
|
2599
|
+
|
|
2600
|
+
### Agent tools via ctx.tools.invokeTool (toolName examples)
|
|
2601
|
+
\`\`\`ts
|
|
2602
|
+
// Helper to unwrap a successful tool reply
|
|
2603
|
+
function unwrapToolValue(resp: any) {
|
|
2604
|
+
if (!resp || resp.type !== 'Reply') {
|
|
2605
|
+
const msg = resp?.message?.value
|
|
2606
|
+
throw new Error(typeof msg === 'string' ? msg : JSON.stringify(resp))
|
|
2607
|
+
}
|
|
2608
|
+
return resp.message.value
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
// askFollowupQuestion
|
|
2612
|
+
const answersText = unwrapToolValue(
|
|
2613
|
+
await ctx.tools.invokeTool({
|
|
2614
|
+
toolName: 'askFollowupQuestion',
|
|
2615
|
+
input: { questions: [{ prompt: 'Which directory?', options: ['src', 'packages'] }] },
|
|
2616
|
+
}),
|
|
2617
|
+
)
|
|
2618
|
+
|
|
2619
|
+
// listFiles
|
|
2620
|
+
const filesText = unwrapToolValue(
|
|
2621
|
+
await ctx.tools.invokeTool({
|
|
2622
|
+
toolName: 'listFiles',
|
|
2623
|
+
input: { path: 'src', recursive: true, maxCount: 2000, includeIgnored: false },
|
|
2624
|
+
}),
|
|
2625
|
+
)
|
|
2626
|
+
|
|
2627
|
+
// searchFiles
|
|
2628
|
+
const hitsText = unwrapToolValue(
|
|
2629
|
+
await ctx.tools.invokeTool({
|
|
2630
|
+
toolName: 'searchFiles',
|
|
2631
|
+
input: { path: '.', regex: 'generateWorkflowCodeWorkflow', filePattern: '*.ts' },
|
|
2632
|
+
}),
|
|
2633
|
+
)
|
|
2634
|
+
|
|
2635
|
+
// fetchUrl
|
|
2636
|
+
const pageText = unwrapToolValue(await ctx.tools.invokeTool({ toolName: 'fetchUrl', input: { url: 'https://example.com' } }))
|
|
2637
|
+
|
|
2638
|
+
// search (web search)
|
|
2639
|
+
const webResults = unwrapToolValue(
|
|
2640
|
+
await ctx.tools.invokeTool({ toolName: 'search', input: { query: 'TypeScript zod schema examples' } }),
|
|
2641
|
+
)
|
|
2642
|
+
|
|
2643
|
+
// executeCommand (provider-backed; may require approval in some environments)
|
|
2644
|
+
const cmdText = unwrapToolValue(
|
|
2645
|
+
await ctx.tools.invokeTool({ toolName: 'executeCommand', input: { command: 'bun test', requiresApproval: false } }),
|
|
2646
|
+
)
|
|
2647
|
+
|
|
2648
|
+
// readFile / writeToFile (provider-backed)
|
|
2649
|
+
const fileText = unwrapToolValue(
|
|
2650
|
+
await ctx.tools.invokeTool({ toolName: 'readFile', input: { path: 'README.md', includeIgnored: false } }),
|
|
2651
|
+
)
|
|
2652
|
+
const writeText = unwrapToolValue(await ctx.tools.invokeTool({ toolName: 'writeToFile', input: { path: 'out.txt', content: 'hi' } }))
|
|
2653
|
+
|
|
2654
|
+
// replaceInFile
|
|
2655
|
+
const diff = ['<<<<<<< SEARCH', 'old', '=======', 'new', '>>>>>>> REPLACE'].join('\\n')
|
|
2656
|
+
const replaceText = unwrapToolValue(await ctx.tools.invokeTool({ toolName: 'replaceInFile', input: { path: 'out.txt', diff } }))
|
|
2657
|
+
|
|
2658
|
+
// removeFile / renameFile
|
|
2659
|
+
const rmText = unwrapToolValue(await ctx.tools.invokeTool({ toolName: 'removeFile', input: { path: 'out.txt' } }))
|
|
2660
|
+
const mvText = unwrapToolValue(
|
|
2661
|
+
await ctx.tools.invokeTool({ toolName: 'renameFile', input: { source_path: 'a.txt', target_path: 'b.txt' } }),
|
|
2662
|
+
)
|
|
2663
|
+
|
|
2664
|
+
// readBinaryFile (returns { type: 'content', value: [...] } in resp.message)
|
|
2665
|
+
const binResp = await ctx.tools.invokeTool({ toolName: 'readBinaryFile', input: { url: 'file://path/to/image.png' } })
|
|
2666
|
+
\`\`\`
|
|
2667
|
+
|
|
2668
|
+
### Sub-workflow example (ctx.runWorkflow)
|
|
2669
|
+
\`\`\`ts
|
|
2670
|
+
const results: any[] = []
|
|
2671
|
+
for (const pr of ctx.state.prs ?? []) {
|
|
2672
|
+
results.push(await ctx.runWorkflow('reviewPR', { prId: pr.id }))
|
|
2673
|
+
}
|
|
2674
|
+
return results
|
|
2675
|
+
\`\`\`
|
|
2676
|
+
|
|
2677
|
+
## Complete Example: High-Quality Step Implementation
|
|
2678
|
+
|
|
2679
|
+
This example demonstrates all quality guidelines in a single step:
|
|
2680
|
+
|
|
2681
|
+
\`\`\`ts
|
|
2682
|
+
// Step: processUserData
|
|
2683
|
+
// Task: Read, validate, and process user data from a file
|
|
2684
|
+
|
|
2685
|
+
// Input validation
|
|
2686
|
+
if (!ctx.input.dataFile) {
|
|
2687
|
+
throw new Error('Missing required input: dataFile')
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2690
|
+
ctx.logger.info(\`Starting user data processing for: \${ctx.input.dataFile}\`)
|
|
2691
|
+
|
|
2692
|
+
// Read file with error handling
|
|
2693
|
+
let rawData
|
|
2694
|
+
try {
|
|
2695
|
+
ctx.logger.debug(\`Reading file: \${ctx.input.dataFile}\`)
|
|
2696
|
+
rawData = await ctx.tools.readFile({ path: ctx.input.dataFile })
|
|
2697
|
+
|
|
2698
|
+
if (!rawData) {
|
|
2699
|
+
throw new Error(\`File not found or empty: \${ctx.input.dataFile}\`)
|
|
2700
|
+
}
|
|
2701
|
+
} catch (error) {
|
|
2702
|
+
const err = error instanceof Error ? error : new Error(String(error))
|
|
2703
|
+
ctx.logger.error(\`Failed to read file: \${err.message}\`)
|
|
2704
|
+
throw err // Preserve original stack trace
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
// Parse and validate data
|
|
2708
|
+
let users
|
|
2709
|
+
try {
|
|
2710
|
+
ctx.logger.debug('Parsing JSON data')
|
|
2711
|
+
const parsed = JSON.parse(rawData)
|
|
2712
|
+
|
|
2713
|
+
if (!parsed?.users || !Array.isArray(parsed.users)) {
|
|
2714
|
+
throw new Error('Invalid data format: expected {users: [...]}')
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
users = parsed.users
|
|
2718
|
+
ctx.logger.info(\`Found \${users.length} users to process\`)
|
|
2719
|
+
} catch (error) {
|
|
2720
|
+
const err = error instanceof Error ? error : new Error(String(error))
|
|
2721
|
+
ctx.logger.error(\`Data parsing failed: \${err.message}\`)
|
|
2722
|
+
throw err // Preserve original stack trace
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
// Process each user with progress reporting
|
|
2726
|
+
const results = []
|
|
2727
|
+
for (let i = 0; i < users.length; i++) {
|
|
2728
|
+
const user = users[i]
|
|
2729
|
+
|
|
2730
|
+
// Validate each user object
|
|
2731
|
+
if (!user?.id || !user?.email) {
|
|
2732
|
+
ctx.logger.warn(\`Skipping invalid user at index \${i}: missing id or email\`)
|
|
2733
|
+
continue
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2736
|
+
// Process user
|
|
2737
|
+
const processed = {
|
|
2738
|
+
id: user.id,
|
|
2739
|
+
email: user.email.toLowerCase().trim(),
|
|
2740
|
+
name: user.name?.trim() || 'Unknown',
|
|
2741
|
+
processedAt: new Date().toISOString(),
|
|
2742
|
+
status: 'active'
|
|
2743
|
+
}
|
|
2744
|
+
|
|
2745
|
+
results.push(processed)
|
|
2746
|
+
|
|
2747
|
+
// Progress feedback every 10 items
|
|
2748
|
+
if ((i + 1) % 10 === 0) {
|
|
2749
|
+
ctx.logger.info(\`Processed \${i + 1}/\${users.length} users\`)
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2753
|
+
ctx.logger.info(\`Successfully processed \${results.length}/\${users.length} users\`)
|
|
2754
|
+
|
|
2755
|
+
// Return structured result with metadata
|
|
2756
|
+
return {
|
|
2757
|
+
users: results,
|
|
2758
|
+
metadata: {
|
|
2759
|
+
totalInput: users.length,
|
|
2760
|
+
totalProcessed: results.length,
|
|
2761
|
+
skipped: users.length - results.length,
|
|
2762
|
+
processedAt: new Date().toISOString()
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
\`\`\`
|
|
2766
|
+
|
|
2767
|
+
Key features demonstrated:
|
|
2768
|
+
- Input validation at start
|
|
2769
|
+
- Comprehensive error handling with try-catch that preserves stack traces
|
|
2770
|
+
- Logging at info, debug, warn, and error levels
|
|
2771
|
+
- Progress reporting for long operations (every 10 items)
|
|
2772
|
+
- Data validation throughout (null checks, type checks, array validation)
|
|
2773
|
+
- Structured return value with metadata for observability
|
|
2774
|
+
- Descriptive error messages with context
|
|
2775
|
+
- Meaningful variable names (rawData, users, processed)
|
|
2776
|
+
- Clean async/await usage
|
|
2777
|
+
- Template literals for readable string interpolation
|
|
2778
|
+
- Proper error type guards (error instanceof Error)
|
|
2779
|
+
|
|
2780
|
+
## Final Instructions
|
|
2781
|
+
|
|
2782
|
+
REMEMBER: The "code" field must be ONLY the function body statements.
|
|
2783
|
+
- DO NOT wrap code in arrow functions: \`(ctx) => { ... }\`
|
|
2784
|
+
- DO NOT wrap code in async functions: \`async (ctx) => { ... }\`
|
|
2785
|
+
- DO NOT include outer curly braces
|
|
2786
|
+
- DO include a return statement if the step should produce output
|
|
2787
|
+
- Each "code" field should be a string containing multiple statements separated by newlines
|
|
2788
|
+
|
|
2789
|
+
Return the complete workflow JSON with the "code" fields populated.
|
|
2790
|
+
`;
|
|
2791
|
+
var generateWorkflowDefinitionWorkflow = async (input, ctx) => {
|
|
2792
|
+
let systemPrompt = WORKFLOW_DEFINITION_SYSTEM_PROMPT;
|
|
2793
|
+
if (input.availableTools && input.availableTools.length > 0) {
|
|
2794
|
+
const toolsList = input.availableTools.map((t) => `- ${t.name}: ${t.description}`).join("\n");
|
|
2795
|
+
systemPrompt += `
|
|
2796
|
+
|
|
2797
|
+
Available Tools:
|
|
2798
|
+
${toolsList}
|
|
2799
|
+
|
|
2800
|
+
Use these tools when appropriate.`;
|
|
2801
|
+
}
|
|
2802
|
+
const result = await ctx.step("generate-workflow-definition", async () => {
|
|
2803
|
+
return agentWorkflow(
|
|
2804
|
+
{
|
|
2805
|
+
systemPrompt,
|
|
2806
|
+
userMessage: [{ role: "user", content: input.prompt }],
|
|
2807
|
+
tools: [],
|
|
2808
|
+
outputSchema: WorkflowFileSchema
|
|
2809
|
+
},
|
|
2810
|
+
ctx
|
|
2811
|
+
);
|
|
2812
|
+
});
|
|
2813
|
+
if (result.type === "Exit" && result.object) {
|
|
2814
|
+
return result.object;
|
|
2815
|
+
}
|
|
2816
|
+
throw new Error("Failed to generate workflow definition");
|
|
2817
|
+
};
|
|
2818
|
+
var generateWorkflowCodeWorkflow = async (input, ctx) => {
|
|
2819
|
+
const result = await ctx.step("generate-workflow-code", async () => {
|
|
2820
|
+
return agentWorkflow(
|
|
2821
|
+
{
|
|
2822
|
+
systemPrompt: WORKFLOW_CODE_SYSTEM_PROMPT,
|
|
2823
|
+
userMessage: [{ role: "user", content: JSON.stringify(input.workflow, null, 2) }],
|
|
2824
|
+
tools: [],
|
|
2825
|
+
outputSchema: WorkflowFileSchema
|
|
2826
|
+
},
|
|
2827
|
+
ctx
|
|
2828
|
+
);
|
|
2829
|
+
});
|
|
2830
|
+
if (result.type === "Exit" && result.object) {
|
|
2831
|
+
return result.object;
|
|
2832
|
+
}
|
|
2833
|
+
throw new Error("Failed to generate workflow code");
|
|
2834
|
+
};
|
|
2835
|
+
|
|
1907
2836
|
// src/workflow/json-ai-types.ts
|
|
1908
2837
|
var toJsonDataContent = (data) => {
|
|
1909
2838
|
if (data instanceof URL) {
|
|
@@ -2102,6 +3031,8 @@ var makeStepFn = () => {
|
|
|
2102
3031
|
};
|
|
2103
3032
|
};
|
|
2104
3033
|
export {
|
|
3034
|
+
GenerateWorkflowCodeInputSchema,
|
|
3035
|
+
GenerateWorkflowDefinitionInputSchema,
|
|
2105
3036
|
MockProvider,
|
|
2106
3037
|
TaskEventKind,
|
|
2107
3038
|
TodoItemSchema,
|
|
@@ -2110,19 +3041,27 @@ export {
|
|
|
2110
3041
|
UpdateTodoItemInputSchema,
|
|
2111
3042
|
UpdateTodoItemOutputSchema,
|
|
2112
3043
|
UsageMeter,
|
|
3044
|
+
WorkflowDefinitionSchema,
|
|
3045
|
+
WorkflowFileSchema,
|
|
3046
|
+
WorkflowInputDefinitionSchema,
|
|
3047
|
+
WorkflowStepDefinitionSchema,
|
|
2113
3048
|
agentWorkflow,
|
|
2114
3049
|
askFollowupQuestion_default as askFollowupQuestion,
|
|
2115
3050
|
computeRateLimitBackoffSeconds,
|
|
2116
3051
|
configSchema,
|
|
2117
3052
|
createContext,
|
|
3053
|
+
createDynamicWorkflow,
|
|
2118
3054
|
executeCommand_default as executeCommand,
|
|
2119
3055
|
fetchUrl_default as fetchUrl,
|
|
2120
3056
|
fromJsonModelMessage,
|
|
3057
|
+
generateWorkflowCodeWorkflow,
|
|
3058
|
+
generateWorkflowDefinitionWorkflow,
|
|
2121
3059
|
getTodoItem_default as getTodoItem,
|
|
2122
3060
|
listFiles_default as listFiles,
|
|
2123
3061
|
listMemoryTopics_default as listMemoryTopics,
|
|
2124
3062
|
listTodoItems_default as listTodoItems,
|
|
2125
3063
|
makeStepFn,
|
|
3064
|
+
parseDynamicWorkflowDefinition,
|
|
2126
3065
|
parseJsonFromMarkdown,
|
|
2127
3066
|
providerModelSchema,
|
|
2128
3067
|
readBinaryFile_default as readBinaryFile,
|