@upstash/workflow
Version:
Durable, Reliable and Performant Serverless Functions
1,631 lines (1,610 loc) • 50.5 kB
text/typescript
import { QstashError, PublishRequest, FlowControl, Client, Receiver, HTTPMethods as HTTPMethods$1 } from '@upstash/qstash';
import { ZodType, z } from 'zod';
import * as ai from 'ai';
import { CoreTool, generateText } from 'ai';
import { createOpenAI } from '@ai-sdk/openai';
/**
* Error raised during Workflow execution
*/
declare class WorkflowError extends QstashError {
constructor(message: string);
}
/**
* Raised when the workflow executes a function successfully
* and aborts to end the execution
*/
declare class WorkflowAbort extends Error {
stepInfo?: Step;
stepName: string;
/**
* whether workflow is to be canceled on abort
*/
cancelWorkflow: boolean;
/**
*
* @param stepName name of the aborting step
* @param stepInfo step information
* @param cancelWorkflow
*/
constructor(stepName: string, stepInfo?: Step, cancelWorkflow?: boolean);
}
/**
* Raised when the workflow is failed due to a non-retryable error
*/
declare class WorkflowNonRetryableError extends WorkflowAbort {
/**
* @param message error message to be displayed
*/
constructor(message?: string);
}
declare const LOG_LEVELS: readonly ["DEBUG", "INFO", "SUBMIT", "WARN", "ERROR"];
type LogLevel = (typeof LOG_LEVELS)[number];
type ChatLogEntry = {
timestamp: number;
workflowRunId: string;
logLevel: LogLevel;
eventType: "ENDPOINT_START" | "SUBMIT_THIRD_PARTY_RESULT" | "CREATE_CONTEXT" | "SUBMIT_FIRST_INVOCATION" | "RUN_SINGLE" | "RUN_PARALLEL" | "SUBMIT_STEP" | "SUBMIT_CLEANUP" | "RESPONSE_WORKFLOW" | "RESPONSE_DEFAULT" | "ERROR";
details: unknown;
};
type WorkflowLoggerOptions = {
logLevel: LogLevel;
logOutput: "console";
};
declare class WorkflowLogger {
private logs;
private options;
private workflowRunId?;
constructor(options: WorkflowLoggerOptions);
log(level: LogLevel, eventType: ChatLogEntry["eventType"], details?: unknown): Promise<void>;
setWorkflowRunId(workflowRunId: string): void;
private writeToConsole;
private shouldLog;
static getLogger(verbose?: boolean | WorkflowLogger): WorkflowLogger | undefined;
}
type HeadersResponse = {
headers: Record<string, string>;
contentType: string;
};
type StepParams = {
context: WorkflowContext;
} & Pick<HeaderParams, "telemetry"> & Required<Pick<HeaderParams, "step" | "invokeCount">>;
type GetHeaderParams = StepParams;
type GetBodyParams = StepParams & Omit<HeadersResponse, "contentType">;
type SubmitStepParams = StepParams & Pick<HeadersResponse, "headers"> & {
body: string;
isParallel: boolean;
};
/**
* Base class outlining steps. Basically, each step kind (run/sleep/sleepUntil)
* should have two methods: getPlanStep & getResultStep.
*
* getPlanStep works the same way for all so it's implemented here.
* The different step types will implement their own getResultStep method.
*/
declare abstract class BaseLazyStep<TResult = unknown> {
readonly stepName: string;
abstract readonly stepType: StepType;
protected abstract readonly allowUndefinedOut: boolean;
constructor(stepName: string);
/**
* plan step to submit when step will run parallel with other
* steps (parallel call state `first`)
*
* @param concurrent number of steps running parallel
* @param targetStep target step id corresponding to this step
* @returns
*/
abstract getPlanStep(concurrent: number, targetStep: number): Step<undefined>;
/**
* result step to submit after the step executes. Used in single step executions
* and when a plan step executes in parallel executions (parallel call state `partial`).
*
* @param concurrent
* @param stepId
*/
abstract getResultStep(concurrent: number, stepId: number): Promise<Step<TResult>>;
/**
* parse the out field of a step result.
*
* will be called when returning the steps to the context from auto executor
*
* @param out field of the step
* @returns parsed out field
*/
parseOut(out: unknown): TResult;
protected safeParseOut(out: string): TResult;
protected static tryParsing(stepOut: unknown): any;
getBody({ step }: GetBodyParams): string;
getHeaders({ context, telemetry, invokeCount, step }: GetHeaderParams): HeadersResponse;
submitStep({ context, body, headers }: SubmitStepParams): Promise<{
messageId: string;
}[]>;
}
declare class AutoExecutor {
private context;
private promises;
private activeLazyStepList?;
private debug?;
private readonly nonPlanStepCount;
private readonly steps;
private indexInCurrentList;
private invokeCount;
private telemetry?;
stepCount: number;
planStepCount: number;
protected executingStep: string | false;
constructor(context: WorkflowContext, steps: Step[], telemetry?: Telemetry, invokeCount?: number, debug?: WorkflowLogger);
/**
* Adds the step function to the list of step functions to run in
* parallel. After adding the function, defers the execution, so
* that if there is another step function to be added, it's also
* added.
*
* After all functions are added, list of functions are executed.
* If there is a single function, it's executed by itself. If there
* are multiple, they are run in parallel.
*
* If a function is already executing (this.executingStep), this
* means that there is a nested step which is not allowed. In this
* case, addStep throws WorkflowError.
*
* @param stepInfo step plan to add
* @returns result of the step function
*/
addStep<TResult>(stepInfo: BaseLazyStep<TResult>): Promise<TResult>;
/**
* Wraps a step function to set this.executingStep to step name
* before running and set this.executingStep to False after execution
* ends.
*
* this.executingStep allows us to detect nested steps which are not
* allowed.
*
* @param stepName name of the step being wrapped
* @param stepFunction step function to wrap
* @returns wrapped step function
*/
wrapStep<TResult = unknown>(stepName: string, stepFunction: StepFunction<TResult>): TResult | Promise<TResult>;
/**
* Executes a step:
* - If the step result is available in the steps, returns the result
* - If the result is not avaiable, runs the function
* - Sends the result to QStash
*
* @param lazyStep lazy step to execute
* @returns step result
*/
protected runSingle<TResult>(lazyStep: BaseLazyStep<TResult>): Promise<TResult>;
/**
* Runs steps in parallel.
*
* @param stepName parallel step name
* @param stepFunctions list of async functions to run in parallel
* @returns results of the functions run in parallel
*/
protected runParallel<TResults extends unknown[]>(parallelSteps: {
[K in keyof TResults]: BaseLazyStep<TResults[K]>;
}): Promise<TResults>;
/**
* Determines the parallel call state
*
* First filters the steps to get the steps which are after `initialStepCount` parameter.
*
* Depending on the remaining steps, decides the parallel state:
* - "first": If there are no steps
* - "last" If there are equal to or more than `2 * parallelStepCount`. We multiply by two
* because each step in a parallel execution will have 2 steps: a plan step and a result
* step.
* - "partial": If the last step is a plan step
* - "discard": If the last step is not a plan step. This means that the parallel execution
* is in progress (there are still steps to run) and one step has finished and submitted
* its result to QStash
*
* @param parallelStepCount number of steps to run in parallel
* @param initialStepCount steps after the parallel invocation
* @returns parallel call state
*/
protected getParallelCallState(parallelStepCount: number, initialStepCount: number): ParallelCallState;
/**
* Get the promise by executing the lazt steps list. If there is a single
* step, we call `runSingle`. Otherwise `runParallel` is called.
*
* @param lazyStepList steps list to execute
* @returns promise corresponding to the execution
*/
private getExecutionPromise;
/**
* @param lazyStepList steps we executed
* @param result result of the promise from `getExecutionPromise`
* @param index index of the current step
* @returns result[index] if lazyStepList > 1, otherwise result
*/
private static getResult;
private deferExecution;
}
type HTTPMethods = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
type ProviderInfo = {
/**
* full url used for request
*/
url: string;
/**
* base url of the request
*/
baseUrl: string;
/**
* route elements which will follow the baseUrl
*/
route: string[];
/**
* headers to include in the request
*/
appendHeaders: Record<string, string>;
/**
* provider owner
*/
owner: Owner;
/**
* method to use in the request
*/
method: HTTPMethods;
};
type Owner = EmailOwner | LLMOwner;
/**
* Email
*/
type EmailOwner = "resend";
/**
* LLM
*/
type LLMOwner = "upstash" | "openai" | "anthropic" | "custom";
/**
* copies and updates the request by removing the api field and adding url & headers.
*
* @param api api field of PublishRequest
* @returns updated request
*/
declare const getProviderInfo: (api: Required<PublishRequest>["api"]) => ProviderInfo;
type ApiCallSettings<TBody = unknown, TFields extends object = object> = Omit<CallSettings<TBody>, "url"> & TFields;
declare abstract class BaseWorkflowApi {
protected context: WorkflowContext;
constructor({ context }: {
context: WorkflowContext;
});
/**
* context.call which uses a QStash API
*
* @param stepName
* @param settings
* @returns
*/
protected callApi<TResult = unknown, TBody = unknown>(stepName: string, settings: ApiCallSettings<TBody, {
api: Parameters<typeof getProviderInfo>[0];
}>): Promise<CallResponse<TResult>>;
}
type CreateChatCompletion$1 = {
model: string;
messages: {
role: "user" | "assistant";
content: unknown;
}[];
max_tokens: number;
metadata?: object;
stop_sequences?: string[];
/**
* streaming is not possible Upstash Workflow.
*/
stream?: false;
system?: string;
temparature?: number;
top_k?: number;
top_p?: number;
};
type ChatCompletion$1 = {
id: string;
type: "message";
role: "assistant";
content: {
type: "text";
text: string;
}[];
model: string;
stop_reasong: string;
stop_sequence: string[];
usage: unknown;
};
declare class AnthropicAPI extends BaseWorkflowApi {
call<TResult = ChatCompletion$1, TBody = CreateChatCompletion$1>(stepName: string, settings: ApiCallSettings<TBody, {
token: string;
operation: "messages.create";
}>): Promise<CallResponse<TResult>>;
}
type Messages = {
content: string;
role: "developer" | "system";
name?: string;
} | {
content: unknown;
role: "user";
name?: string;
} | {
content: unknown;
refusal?: string;
role: "assistant";
name?: string;
audio?: unknown;
tool_calls?: unknown;
} | {
role: "tool";
content: string | unknown;
tool_call_id: string;
} | {
role: "function";
content: string | undefined;
name: string;
};
type CreateChatCompletion = {
messages: Messages[];
model: string;
store?: boolean;
reasoning_effort?: string;
metadata?: unknown;
frequency_penalty?: number;
logit_bias?: Record<string, number>;
logprobs?: boolean;
top_logprobs?: number;
max_completion_tokens?: number;
n?: number;
modalities?: string[];
prediction?: unknown;
audio?: unknown;
presence_penalty?: number;
response_format?: unknown;
seed?: number;
service_tier?: string;
stop?: string | string[];
/**
* streaming is not supported in Upstash Workflow.
*/
stream?: false;
temperature?: number;
top_p?: number;
tools?: unknown;
tool_choice?: string;
parallel_tool_calls?: boolean;
user?: string;
};
type ChatCompletion = {
id: string;
choices: ChatCompletionChoice[];
created: number;
model: string;
object: "chat.completion";
service_tier?: "scale" | "default" | null;
system_fingerprint?: string;
usage?: unknown;
};
type ChatCompletionChoice = {
finish_reason: "stop" | "length" | "tool_calls" | "content_filter" | "function_call";
index: number;
logprobs: unknown;
message: {
content: string | null;
refusal: string | null;
role: "assistant";
audio?: unknown;
tool_calls?: unknown;
};
};
declare class OpenAIAPI extends BaseWorkflowApi {
call<TResult = ChatCompletion, TBody = CreateChatCompletion>(stepName: string, settings: ApiCallSettings<TBody, {
token: string;
organization?: string;
baseURL?: string;
operation: "chat.completions.create";
}>): Promise<CallResponse<TResult>>;
}
type SendEmail = {
from: string;
to: string;
subject: string;
bcc?: string | string[];
cc?: string | string[];
scheduled_at?: string;
reply_to?: string | string[];
html?: string;
text?: string;
headers: unknown;
attachments: unknown;
tags: {
name: string;
value: string;
}[];
};
type SendEmailResponse = {
id: string;
};
type SendBatchEmail = SendEmail[];
type SendBatchEmailResponse = {
data: SendEmailResponse[];
};
declare class ResendAPI extends BaseWorkflowApi {
call<TBatch extends boolean = false, TResult = TBatch extends true ? SendBatchEmailResponse : SendEmailResponse, TBody = TBatch extends true ? SendBatchEmail : SendEmail>(stepName: string, settings: ApiCallSettings<TBody, {
token: string;
batch?: TBatch;
}>): Promise<CallResponse<TResult>>;
}
declare class WorkflowApi extends BaseWorkflowApi {
get openai(): OpenAIAPI;
get resend(): ResendAPI;
get anthropic(): AnthropicAPI;
}
/**
* An Agent which utilizes the model and tools available to it
* to achieve a given task
*
* @param name Name of the agent
* @param background Background of the agent
* @param model LLM model to use
* @param tools tools available to the agent
* @param maxSteps number of times the agent can call the LLM at most. If
* the agent abruptly stops execution after calling tools, you may need
* to increase maxSteps
* @param temparature temparature used when calling the LLM
*/
declare class Agent {
readonly name: AgentParameters["name"];
readonly tools: AgentParameters["tools"];
readonly maxSteps: AgentParameters["maxSteps"];
readonly background: AgentParameters["background"];
readonly model: AgentParameters["model"];
readonly temparature: AgentParameters["temparature"];
private readonly context;
constructor({ tools, maxSteps, background, name, model, temparature }: AgentParameters, context: WorkflowContext);
/**
* Trigger the agent by passing a prompt
*
* @param prompt task to assign to the agent
* @returns Response as `{ text: string }`
*/
call({ prompt }: {
prompt: string;
}): Promise<{
text: string;
}>;
/**
* Convert the agent to a tool which can be used by other agents.
*
* @returns the agent as a tool
*/
asTool(): AISDKTool;
}
declare class WorkflowTool<TSchema extends ZodType = ZodType> implements LangchainTool {
/**
* description of the tool
*/
readonly description: string;
/**
* schema of the tool
*/
readonly schema: TSchema;
/**
* function to invoke the tool
*/
readonly invoke: (params: z.infer<TSchema>) => any;
/**
* whether the invoke method of the tool is to be wrapped with `context.run`
*/
readonly executeAsStep: boolean;
/**
*
* @param description description of the tool
* @param schema schema of the tool
* @param invoke function to invoke the tool
* @param executeAsStep whether the invoke method of the tool is to be wrapped with `context.run`
*/
constructor(params: {
/**
* description of the tool
*/
description: string;
/**
* schema of the tool
*/
schema: TSchema;
/**
* invoke function to invoke the tool
*/
invoke: (params: z.infer<TSchema>) => any;
/**
* whether the invoke method is to be wrapped with `context.run`.
*
* When you pass a LangChain, AI SDK tool or a WorkflowTool to your agent,
* the execute/invoke method of the tool is wrapped with `context.run` by default.
*
* This option allows you to disable this behavior.
*
* You may want to disable wrapping with context.run if you want to run context.run,
* context.call or any other workflow step yourself in the execute/invoke method
* of the tool.
*
* @default true
*/
executeAsStep?: boolean;
});
}
type AISDKTool = CoreTool;
type LangchainTool = {
description: string;
schema: AISDKTool["parameters"];
invoke: (...params: any[]) => any;
};
type GenerateTextParams = Parameters<typeof generateText>[0];
type Model = GenerateTextParams["model"];
type AgentParameters<TTool extends AISDKTool | LangchainTool | WorkflowTool = AISDKTool> = {
/**
* number of times the agent can call the LLM at most. If
* the agent abruptly stops execution after calling tools, you may need
* to increase maxSteps
*/
maxSteps: number;
/**
* Background of the agent
*/
background: string;
/**
* tools available to the agent
*/
tools: Record<string, TTool>;
/**
* Name of the agent
*/
name: string;
/**
* LLM model to use
*/
model: Model;
/**
* temparature used when calling the LLM
*
* @default 0.1
*/
temparature?: number;
};
type TaskParams = {
/**
* task assigned to the agent
*/
prompt: string;
};
type SingleAgentTaskParams = TaskParams & {
/**
* agent to perform the task
*/
agent: Agent;
};
type MultiAgentTaskParams = TaskParams & {
/**
* Agents which will collaborate to achieve the task
*/
agents: Agent[];
/**
* number of times the manager agent can call the LLM at most.
* If the agent abruptly stops execution after calling other agents, you may
* need to increase maxSteps
*/
maxSteps: number;
/**
* LLM model to use
*/
model: Model;
/**
* Background of the agent. If not passed, default will be used.
*/
background?: string;
};
type ModelParams = Parameters<ReturnType<typeof createOpenAI>>;
type AgentCallParams = Pick<CallSettings, "flowControl" | "retries" | "timeout" | "retryDelay">;
type CustomModelSettings = ModelParams["1"] & {
baseURL?: string;
apiKey?: string;
} & {
callSettings: AgentCallParams;
};
type CustomModelParams = [ModelParams[0], CustomModelSettings?];
type ProviderFunction = (params: {
fetch: typeof fetch;
}) => any;
/**
* An Agent Task
*
* Can be run to make the agent(s) complete it using the tools available to them
*
* Can consist of a single agent or multiple agents.
*
* Single agent:
*
* ```ts
* const task = context.agents.task({
* agent: researcherAgent,
* prompt: "Tell me about 5 topics in advanced physics.",
* });
* const { text } = await task.run();
* ```
*
* Multi Agent:
*
* ```ts
* const task = context.agents.task({
* model,
* maxSteps: 3,
* agents: [researcherAgent, mathAgent],
* prompt: "Tell me about 3 cities in Japan and calculate the sum of their populations",
* });
* const { text } = await task.run();
* ```
*/
declare class Task {
private readonly context;
private readonly taskParameters;
constructor({ context, taskParameters, }: {
context: WorkflowContext;
taskParameters: SingleAgentTaskParams | MultiAgentTaskParams;
});
/**
* Run the agents to complete the task
*
* @returns Result of the task as { text: string }
*/
run(): Promise<{
text: string;
}>;
}
/**
* Workflow Agents API
*
* https://upstash.com/docs/workflow/agents/overview
*
* Allows defining agents which can complete a given task
* using tools available to them.
*/
declare class WorkflowAgents {
private context;
constructor({ context }: {
context: WorkflowContext;
});
/**
* Defines an agent
*
* ```ts
* const researcherAgent = context.agents.agent({
* model,
* name: 'academic',
* maxSteps: 2,
* tools: {
* wikiTool: new WikipediaQueryRun({
* topKResults: 1,
* maxDocContentLength: 500,
* })
* },
* background:
* 'You are researcher agent with access to Wikipedia. ' +
* 'Utilize Wikipedia as much as possible for correct information',
* });
* ```
*
* @param params agent parameters
* @returns
*/
agent(params: AgentParameters<AISDKTool | LangchainTool>): Agent;
/**
* Defines a task to be executed by a single agent
*
* ```ts
* const task = context.agents.task({
* agent: researcherAgent,
* prompt: "Tell me about 5 topics in advanced physics.",
* });
* ```
*/
task(taskParameters: SingleAgentTaskParams): Task;
/**
* Defines a task to be executed by multiple collaborating agents
*
* ```ts
* const task = context.agents.task({
* model,
* maxSteps: 3,
* agents: [researcherAgent, mathAgent],
* prompt: "Tell me about 3 cities in Japan and calculate the sum of their populations",
* });
* ```
*/
task(taskParameters: MultiAgentTaskParams): Task;
/**
* creates an openai model for agents
*/
openai(...params: CustomModelParams): ai.LanguageModelV1;
AISDKModel: <TProvider extends ProviderFunction>({ context, provider, providerParams, agentCallParams, }: {
context: WorkflowContext;
provider: TProvider;
providerParams?: Omit<Required<Parameters<TProvider>>[0], "fetch">;
agentCallParams?: AgentCallParams;
}) => ReturnType<TProvider>;
}
/**
* Upstash Workflow context
*
* See the docs for fields and methods https://upstash.com/docs/qstash/workflows/basics/context
*/
declare class WorkflowContext<TInitialPayload = unknown> {
protected readonly executor: AutoExecutor;
protected readonly steps: Step[];
/**
* QStash client of the workflow
*
* Can be overwritten by passing `qstashClient` parameter in `serve`:
*
* ```ts
* import { Client } from "@upstash/qstash"
*
* export const POST = serve(
* async (context) => {
* ...
* },
* {
* qstashClient: new Client({...})
* }
* )
* ```
*/
readonly qstashClient: WorkflowClient;
/**
* Run id of the workflow
*/
readonly workflowRunId: string;
/**
* URL of the workflow
*
* Can be overwritten by passing a `url` parameter in `serve`:
*
* ```ts
* export const POST = serve(
* async (context) => {
* ...
* },
* {
* url: "new-url-value"
* }
* )
* ```
*/
readonly url: string;
/**
* URL to call in case of workflow failure with QStash failure callback
*
* https://upstash.com/docs/qstash/features/callbacks#what-is-a-failure-callback
*
* Can be overwritten by passing a `failureUrl` parameter in `serve`:
*
* ```ts
* export const POST = serve(
* async (context) => {
* ...
* },
* {
* failureUrl: "new-url-value"
* }
* )
* ```
*/
readonly failureUrl?: string;
/**
* Payload of the request which started the workflow.
*
* To specify its type, you can define `serve` as follows:
*
* ```ts
* // set requestPayload type to MyPayload:
* export const POST = serve<MyPayload>(
* async (context) => {
* ...
* }
* )
* ```
*
* By default, `serve` tries to apply `JSON.parse` to the request payload.
* If your payload is encoded in a format other than JSON, you can utilize
* the `initialPayloadParser` parameter:
*
* ```ts
* export const POST = serve<MyPayload>(
* async (context) => {
* ...
* },
* {
* initialPayloadParser: (initialPayload) => {return doSomething(initialPayload)}
* }
* )
* ```
*/
readonly requestPayload: TInitialPayload;
/**
* headers of the initial request
*/
readonly headers: Headers;
/**
* Map of environment variables and their values.
*
* Can be set using the `env` option of serve:
*
* ```ts
* export const POST = serve<MyPayload>(
* async (context) => {
* const key = context.env["API_KEY"];
* },
* {
* env: {
* "API_KEY": "*****";
* }
* }
* )
* ```
*
* Default value is set to `process.env`.
*/
readonly env: Record<string, string | undefined>;
/**
* Number of retries
*/
readonly retries: number;
/**
* Delay between retries.
*
* By default, the `retryDelay` is exponential backoff.
* More details can be found in: https://upstash.com/docs/qstash/features/retry.
*
* The `retryDelay` option allows you to customize the delay (in milliseconds) between retry attempts when message delivery fails.
*
* You can use mathematical expressions and the following built-in functions to calculate the delay dynamically.
* The special variable `retried` represents the current retry attempt count (starting from 0).
*
* Supported functions:
* - `pow`
* - `sqrt`
* - `abs`
* - `exp`
* - `floor`
* - `ceil`
* - `round`
* - `min`
* - `max`
*
* Examples of valid `retryDelay` values:
* ```ts
* 1000 // 1 second
* 1000 * (1 + retried) // 1 second multiplied by the current retry attempt
* pow(2, retried) // 2 to the power of the current retry attempt
* max(10, pow(2, retried)) // The greater of 10 or 2^retried
* ```
*/
readonly retryDelay?: string;
/**
* Settings for controlling the number of active requests
* and number of requests per second with the same key.
*/
readonly flowControl?: FlowControl;
/**
* Label to apply to the workflow run.
*
* Can be used to filter the workflow run logs.
*
* Can be set by passing a `label` parameter when triggering the workflow
* with `client.trigger`:
*
* ```ts
* await client.trigger({
* url: "https://workflow-endpoint.com",
* label: "my-label"
* });
* ```
*/
readonly label?: string;
constructor({ qstashClient, workflowRunId, headers, steps, url, failureUrl, debug, initialPayload, env, retries, retryDelay, telemetry, invokeCount, flowControl, label, }: {
qstashClient: WorkflowClient;
workflowRunId: string;
headers: Headers;
steps: Step[];
url: string;
failureUrl?: string;
debug?: WorkflowLogger;
initialPayload: TInitialPayload;
env?: Record<string, string | undefined>;
retries?: number;
retryDelay?: string;
telemetry?: Telemetry;
invokeCount?: number;
flowControl?: FlowControl;
label?: string;
});
/**
* Executes a workflow step
*
* ```typescript
* const result = await context.run("step 1", () => {
* return "result"
* })
* ```
*
* Can also be called in parallel and the steps will be executed
* simulatenously:
*
* ```typescript
* const [result1, result2] = await Promise.all([
* context.run("step 1", () => {
* return "result1"
* }),
* context.run("step 2", async () => {
* return await fetchResults()
* })
* ])
* ```
*
* @param stepName name of the step
* @param stepFunction step function to be executed
* @returns result of the step function
*/
run<TResult>(stepName: string, stepFunction: StepFunction<TResult>): Promise<TResult>;
/**
* Stops the execution for the duration provided.
*
* ```typescript
* await context.sleep('sleep1', 3) // wait for three seconds
* ```
*
* @param stepName
* @param duration sleep duration in seconds
* @returns undefined
*/
sleep(stepName: string, duration: number | Duration): Promise<void>;
/**
* Stops the execution until the date time provided.
*
* ```typescript
* await context.sleepUntil('sleep1', Date.now() / 1000 + 3) // wait for three seconds
* ```
*
* @param stepName
* @param datetime time to sleep until. Can be provided as a number (in unix seconds),
* as a Date object or a string (passed to `new Date(datetimeString)`)
* @returns undefined
*/
sleepUntil(stepName: string, datetime: Date | string | number): Promise<void>;
/**
* Makes a third party call through QStash in order to make a
* network call without consuming any runtime.
*
* ```ts
* const { status, body } = await context.call<string>(
* "post call step",
* {
* url: "https://www.some-endpoint.com/api",
* method: "POST",
* body: "my-payload"
* }
* );
* ```
*
* tries to parse the result of the request as JSON. If it's
* not a JSON which can be parsed, simply returns the response
* body as it is.
*
* @param stepName
* @param url url to call
* @param method call method. "GET" by default.
* @param body call body
* @param headers call headers
* @param retries number of call retries. 0 by default
* @param retryDelay delay / time gap between retries.
* @param timeout max duration to wait for the endpoint to respond. in seconds.
* @returns call result as {
* status: number;
* body: unknown;
* header: Record<string, string[]>
* }
*/
call<TResult = unknown, TBody = unknown>(stepName: string, settings: CallSettings<TBody>): Promise<CallResponse<TResult>>;
call<TResult extends {
workflowRunId: string;
} = {
workflowRunId: string;
}, TBody = unknown>(stepName: string, settings: LazyInvokeStepParams<TBody, unknown> & Pick<CallSettings, "timeout">): Promise<CallResponse<TResult>>;
/**
* Pauses workflow execution until a specific event occurs or a timeout is reached.
*
*```ts
* const result = await workflow.waitForEvent("payment-confirmed", "payment.confirmed", {
* timeout: "5m"
* });
*```
*
* To notify a waiting workflow:
*
* ```ts
* import { Client } from "@upstash/workflow";
*
* const client = new Client({ token: "<QSTASH_TOKEN>" });
*
* await client.notify({
* eventId: "payment.confirmed",
* data: {
* amount: 99.99,
* currency: "USD"
* }
* })
* ```
*
* Alternatively, you can use the `context.notify` method.
*
* @param stepName
* @param eventId - Unique identifier for the event to wait for
* @param options - Configuration options.
* @returns `{ timeout: boolean, eventData: TEventData }`.
* The `timeout` property specifies if the workflow has timed out. The `eventData`
* is the data passed when notifying this workflow of an event.
*/
waitForEvent<TEventData = unknown>(stepName: string, eventId: string, options?: WaitEventOptions): Promise<WaitStepResponse<TEventData>>;
/**
* Notify workflow runs waiting for an event
*
* ```ts
* const { eventId, eventData, notifyResponse } = await context.notify(
* "notify step", "event-id", "event-data"
* );
* ```
*
* Upon `context.notify`, the workflow runs waiting for the given eventId (context.waitForEvent)
* will receive the given event data and resume execution.
*
* The response includes the same eventId and eventData. Additionally, there is
* a notifyResponse field which contains a list of `Waiter` objects, each corresponding
* to a notified workflow run.
*
* @param stepName
* @param eventId event id to notify
* @param eventData event data to notify with
* @returns notify response which has event id, event data and list of waiters which were notified
*/
notify(stepName: string, eventId: string, eventData: unknown): Promise<NotifyStepResponse>;
invoke<TInitialPayload, TResult>(stepName: string, settings: LazyInvokeStepParams<TInitialPayload, TResult>): Promise<InvokeStepResponse<TResult>>;
/**
* Cancel the current workflow run
*
* Will throw WorkflowAbort to stop workflow execution.
* Shouldn't be inside try/catch.
*/
cancel(): Promise<void>;
/**
* Adds steps to the executor. Needed so that it can be overwritten in
* DisabledWorkflowContext.
*/
protected addStep<TResult = unknown>(step: BaseLazyStep<TResult>): Promise<TResult>;
get api(): WorkflowApi;
get agents(): WorkflowAgents;
}
/**
* Interface for Client with required methods
*
* Neeeded to resolve import issues
*/
type WorkflowClient = {
batch: InstanceType<typeof Client>["batch"];
batchJSON: InstanceType<typeof Client>["batchJSON"];
publishJSON: InstanceType<typeof Client>["publishJSON"];
publish: InstanceType<typeof Client>["publish"];
http: InstanceType<typeof Client>["http"];
};
/**
* Interface for Receiver with required methods
*
* Neeeded to resolve import issues
*/
type WorkflowReceiver = {
verify: InstanceType<typeof Receiver>["verify"];
};
declare const StepTypes: readonly ["Initial", "Run", "SleepFor", "SleepUntil", "Call", "Wait", "Notify", "Invoke"];
type StepType = (typeof StepTypes)[number];
type ThirdPartyCallFields<TBody = unknown> = {
/**
* Third party call URL. Set when context.call is used.
*/
callUrl: string;
/**
* Third party call method. Set when context.call is used.
*/
callMethod: HTTPMethods$1;
/**
* Third party call body. Set when context.call is used.
*/
callBody: TBody;
/**
* Third party call headers. Set when context.call is used.
*/
callHeaders: Record<string, string>;
};
type WaitFields = {
waitEventId: string;
timeout: string;
waitTimeout?: boolean;
};
type NotifyFields = {
notifyEventId?: string;
eventData?: string;
};
type Step<TResult = unknown, TBody = unknown> = {
/**
* index of the step
*/
stepId: number;
/**
* name of the step
*/
stepName: string;
/**
* type of the step (Initial/Run/SleepFor/SleepUntil/Call)
*/
stepType: StepType;
/**
* step result. Set if context.run or context.call are used.
*/
out?: TResult;
/**
* sleep duration in seconds. Set when context.sleep is used.
*/
sleepFor?: number | Duration;
/**
* unix timestamp (in seconds) to wait until. Set when context.sleepUntil is used.
*/
sleepUntil?: number;
/**
* number of steps running concurrently if the step is in a parallel run.
* Set to 1 if step is not parallel.
*/
concurrent: number;
/**
* target step of a plan step. In other words, the step to assign the
* result of a plan step.
*
* undefined if the step is not a plan step (of a parallel run). Otherwise,
* set to the target step.
*/
targetStep?: number;
} & (ThirdPartyCallFields<TBody> | {
[P in keyof ThirdPartyCallFields]?: never;
}) & (WaitFields | {
[P in keyof WaitFields]?: never;
}) & (NotifyFields | {
[P in keyof NotifyFields]?: never;
});
type RawStep = {
messageId: string;
body: string;
callType: "step" | "toCallback" | "fromCallback";
};
type SyncStepFunction<TResult> = () => TResult;
type AsyncStepFunction<TResult> = () => Promise<TResult>;
type StepFunction<TResult> = AsyncStepFunction<TResult> | SyncStepFunction<TResult>;
type ParallelCallState = "first" | "partial" | "discard" | "last";
type RouteFunction<TInitialPayload, TResult = unknown> = (context: WorkflowContext<TInitialPayload>) => Promise<TResult>;
type FinishCondition = "success" | "duplicate-step" | "fromCallback" | "auth-fail" | "failure-callback" | "workflow-already-ended" | WorkflowNonRetryableError;
type DetailedFinishCondition = {
condition: Exclude<FinishCondition, WorkflowNonRetryableError | "failure-callback">;
result?: never;
} | {
condition: "non-retryable-error";
result: WorkflowNonRetryableError;
} | {
condition: "failure-callback";
result: string | void;
};
type WorkflowServeOptions<TResponse extends Response = Response, TInitialPayload = unknown> = ValidationOptions<TInitialPayload> & {
/**
* QStash client
*/
qstashClient?: WorkflowClient;
/**
* Function called to return a response after each step execution
*
* @param workflowRunId
* @returns response
*/
onStepFinish?: (workflowRunId: string, finishCondition: FinishCondition, detailedFinishCondition?: DetailedFinishCondition) => TResponse;
/**
* Url of the endpoint where the workflow is set up.
*
* If not set, url will be inferred from the request.
*/
url?: string;
/**
* Verbose mode
*
* Disabled if not set. If set to true, a logger is created automatically.
*
* Alternatively, a WorkflowLogger can be passed.
*/
verbose?: WorkflowLogger | true;
/**
* Receiver to verify *all* requests by checking if they come from QStash
*
* By default, a receiver is created from the env variables
* QSTASH_CURRENT_SIGNING_KEY and QSTASH_NEXT_SIGNING_KEY if they are set.
*/
receiver?: WorkflowReceiver;
/**
* Url to call if QStash retries are exhausted while executing the workflow
*/
failureUrl?: string;
/**
* Error handler called when an error occurs in the workflow. This is
* different from `failureFunction` in that it is called when an error
* occurs in the workflow, while `failureFunction` is called when QStash
* retries are exhausted.
*/
onError?: (error: Error) => void;
/**
* Failure function called when QStash retries are exhausted while executing
* the workflow. Will overwrite `failureUrl` parameter with the workflow
* endpoint if passed.
*
* @param context workflow context at the moment of error
* @param failStatus error status
* @param failResponse error message
* @returns void
*/
failureFunction?: (failureData: {
context: Omit<WorkflowContext<TInitialPayload>, "run" | "sleepUntil" | "sleep" | "call" | "waitForEvent" | "notify" | "cancel" | "api" | "invoke" | "agents">;
failStatus: number;
failResponse: string;
failHeaders: Record<string, string[]>;
failStack: string;
}) => Promise<void | string> | void | string;
/**
* Base Url of the workflow endpoint
*
* Can be used to set if there is a local tunnel or a proxy between
* QStash and the workflow endpoint.
*
* Will be set to the env variable UPSTASH_WORKFLOW_URL if not passed.
* If the env variable is not set, the url will be infered as usual from
* the `request.url` or the `url` parameter in `serve` options.
*
* @default undefined
*/
baseUrl?: string;
/**
* Optionally, one can pass an env object mapping environment
* variables to their keys.
*
* Useful in cases like cloudflare with hono.
*/
env?: Record<string, string | undefined>;
/**
* Number of retries to use in workflow requests
*
* @default 3
*/
retries?: number;
/**
* Delay between retries.
*
* By default, the `retryDelay` is exponential backoff.
* More details can be found in: https://upstash.com/docs/qstash/features/retry.
*
* The `retryDelay` option allows you to customize the delay (in milliseconds) between retry attempts when message delivery fails.
*
* You can use mathematical expressions and the following built-in functions to calculate the delay dynamically.
* The special variable `retried` represents the current retry attempt count (starting from 0).
*
* Supported functions:
* - `pow`
* - `sqrt`
* - `abs`
* - `exp`
* - `floor`
* - `ceil`
* - `round`
* - `min`
* - `max`
*
* Examples of valid `retryDelay` values:
* ```ts
* 1000 // 1 second
* 1000 * (1 + retried) // 1 second multiplied by the current retry attempt
* pow(2, retried) // 2 to the power of the current retry attempt
* max(10, pow(2, retried)) // The greater of 10 or 2^retried
* ```
*/
retryDelay?: string;
/**
* Whether the framework should use `content-type: application/json`
* in `triggerFirstInvocation`.
*
* Not part of the public API. Only available in serveBase, which is not exported.
*/
useJSONContent?: boolean;
/**
* By default, Workflow SDK sends telemetry about SDK version, framework or runtime.
*
* Set `disableTelemetry` to disable this behavior.
*
* @default false
*/
disableTelemetry?: boolean;
/**
* Settings for controlling the number of active requests
* and number of requests per second with the same key.
*/
flowControl?: FlowControl;
} & ValidationOptions<TInitialPayload>;
type ValidationOptions<TInitialPayload> = {
schema?: z.ZodType<TInitialPayload>;
initialPayloadParser?: (initialPayload: string) => TInitialPayload;
};
type ExclusiveValidationOptions<TInitialPayload> = {
schema?: ValidationOptions<TInitialPayload>["schema"];
initialPayloadParser?: never;
} | {
schema?: never;
initialPayloadParser?: ValidationOptions<TInitialPayload>["initialPayloadParser"];
};
type Telemetry = {
/**
* sdk version
*/
sdk: string;
/**
* platform (such as nextjs/cloudflare)
*/
framework?: string;
/**
* node version
*/
runtime?: string;
};
type PublicServeOptions<TInitialPayload = unknown, TResponse extends Response = Response> = Omit<WorkflowServeOptions<TResponse, TInitialPayload>, "onStepFinish" | "useJSONContent" | "schema" | "initialPayloadParser"> & ExclusiveValidationOptions<TInitialPayload>;
/**
* Payload passed as body in failureFunction
*/
type FailureFunctionPayload = {
/**
* error name
*/
error: string;
/**
* error message
*/
message: string;
/**
* error stack trace if available
*/
stack?: string;
};
/**
* Makes all fields except the ones selected required
*/
type RequiredExceptFields<T, K extends keyof T> = Omit<Required<T>, K> & Partial<Pick<T, K>>;
type Waiter = {
url: string;
deadline: number;
headers: Record<string, string[]>;
timeoutUrl?: string;
timeoutBody?: unknown;
timeoutHeaders?: Record<string, string[]>;
};
type NotifyResponse = {
waiter: Waiter;
messageId: string;
error: string;
};
type WaitRequest = {
url: string;
step: Step;
timeout: string;
timeoutUrl?: string;
timeoutBody?: string;
timeoutHeaders?: Record<string, string[]>;
};
type WaitStepResponse<TEventData = unknown> = {
/**
* whether the wait for event step timed out. false if
* the step is notified
*/
timeout: boolean;
/**
* body passed in notify request
*/
eventData: TEventData;
};
type NotifyStepResponse = {
/**
* notified event id
*/
eventId: string;
/**
* event data sent with notify
*/
eventData: unknown;
/**
* response from notify
*/
notifyResponse: NotifyResponse[];
};
type CallResponse<TResult = unknown> = {
status: number;
body: TResult;
header: Record<string, string[]>;
};
/**
* Valid duration string formats
* @example "30s" // 30 seconds
* @example "5m" // 5 minutes
* @example "2h" // 2 hours
* @example "1d" // 1 day
*/
type Duration = `${bigint}${"s" | "m" | "h" | "d"}`;
interface WaitEventOptions {
/**
* Duration in seconds to wait for an event before timing out the workflow.
* @example 300 // 5 minutes in seconds
* @example "5m" // 5 minutes as duration string
* @default "7d"
*/
timeout?: number | Duration;
}
type StringifyBody<TBody = unknown> = TBody extends string ? boolean : true;
type CallSettings<TBody = unknown> = {
url: string;
method?: HTTPMethods$1;
/**
* Request body.
*
* By default, the body is stringified with `JSON.stringify`. If you want
* to send a string body without stringifying it, you need to set
* `stringifyBody` to false.
*/
body?: TBody;
headers?: Record<string, string>;
retries?: number;
retryDelay?: string;
timeout?: Duration | number;
flowControl?: FlowControl;
/**
* Whether the body field should be stringified when making the request.
*
* @default true
*/
stringifyBody?: StringifyBody<TBody>;
};
type HeaderParams = {
/**
* whether the request is a first invocation request.
*/
initHeaderValue: "true" | "false";
/**
* run id of the workflow
*/
workflowRunId: string;
/**
* url where the workflow is hosted
*/
workflowUrl: string;
/**
* user headers which will be forwarded in the request
*/
userHeaders?: Headers;
/**
* failure url to call incase of failure
*/
failureUrl?: WorkflowServeOptions["failureUrl"];
/**
* retry setting of requests except context.call
*/
retries?: number;
/**
* retry delay to include in headers.
*/
retryDelay?: string;
/**
* telemetry to include in timeoutHeaders.
*
* Only needed/used when the step is a waitForEvent step
*/
telemetry?: Telemetry;
/**
* invoke count to include in headers
*/
invokeCount?: number;
/**
* Settings for controlling the number of active requests
* and number of requests per second with the same key.
*/
flowControl?: FlowControl;
} & ({
/**
* step to generate headers for
*/
step: Step;
/**
* number of retries in context.call
*/
callRetries?: number;
/**
* retry delay to include in headers.
*/
callRetryDelay?: string;
/**
* timeout duration in context.call
*/
callTimeout?: number | Duration;
/**
* Settings for controlling the number of active requests
* and number of requests per second with the same key.
*
* will be passed in context.call.
*/
callFlowControl?: FlowControl;
} | {
/**
* step not passed. Either first invocation or simply getting headers for
* third party callack.
*/
step?: never;
/**
* number of retries in context.call
*
* set to never because this is not a context.call step
*/
callRetries?: never;
/**
* retry delay to include in headers.
*
* set to never because this is not a context.call step
*/
callRetryDelay?: never;
/**
* timeout duration in context.call
*
* set to never because this is not a context.call step
*/
callTimeout?: never;
/**
* Settings for controlling the number of active requests
* and number of requests per second with the same key.
*
* will be passed in context.call.
*/
callFlowControl?: never;
});
type InvokeWorkflowRequest = {
workflowUrl: string;
workflowRunId: string;
headers: Record<string, string[]>;
step: Step;
body: string;
};
type LazyInvokeStepParams<TInitiaPayload, TResult> = {
workflow: Pick<InvokableWorkflow<TInitiaPayload, TResult>, "routeFunction" | "workflowId" | "options">;
body: TInitiaPayload;
workflowRunId?: string;
} & Pick<CallSettings<TInitiaPayload>, "retries" | "headers" | "flowControl" | "retryDelay" | "stringifyBody">;
type InvokeStepResponse<TBody> = {
body: TBody;
isCanceled?: boolean;
isFailed?: boolean;
};
type InvokableWorkflow<TInitialPayload, TResult> = {
routeFunction: RouteFunction<TInitialPayload, TResult>;
options: WorkflowServeOptions<Response, TInitialPayload>;
workflowId?: string;
};
export { type AsyncStepFunction as A, type WorkflowLoggerOptions as B, type CallResponse as C, type DetailedFinishCondition as D, type ExclusiveValidationOptions as E, type FinishCondition as F, WorkflowLogger as G, type HeaderParams as H, type InvokeWorkflowRequest as I, type LazyInvokeStepParams as L, type NotifyResponse as N, type ParallelCallState as P, type RouteFunction as R, type StepType as S, type Telemetry as T, type WorkflowServeOptions as W, type RawStep as a, type Waiter as b, WorkflowError as c, WorkflowAbort as d, WorkflowNonRetryableError as e, WorkflowTool as f, WorkflowContext