Runtime Options
As mentioned in Task Registration page tasks can be configured with different options during registration.
However, in some cases, task options need to be determined at runtime depending on the request parameters (e.g. items count, AI model, etc...).
To support this you can override the TaskExecutor::options method and resolve TaskOptions dynamically.
@Executor("example-task")
export class ExampleExecutor implements TaskExecutor {
async options(exampleCommand: ExampleCommand, flow: OptionsFlow): Promise<TaskOptions> {
const toolId = exampleCommand.model === 'model1' ?
'model1_tool_id' : 'default_toolId';
const totalUsageAmount = countTotalToolUsageAmount();
return {
monetization: {
toolId
},
usageAmount: totalUsageAmount,
}
}
}
The second argument is flow: OptionsFlow — a narrower variant of Flow exposing what's needed to resolve pricing inputs:
flow.config()— read workflow configuration (e.g. picktoolIdbased on selected model).flow.fileMetadata(params)— invokes thev1/files/metadatatask to inspect a file before deciding cost.flow.faceDetection(params)— invokesINSTANT_PERSONALIZED_FACE; useful when pricing depends on detected faces.
async options(cmd: ExampleCommand, flow: OptionsFlow): Promise<TaskOptions> {
const cfg = await flow.config().get();
const toolId = cfg.model === 'premium' ? 'premium_tool_id' : 'default_tool_id';
return { monetization: { toolId } };
}
To enable the above implementation, make sure the task has enablePreflight set to true.
As long as the sub-task has enablePreflight: true, options() is called whenever it runs — regardless of whether sub-task charging is enabled (i.e. independent of the caller's enableSubTaskCredits or a per-call chargeCredits). When the sub-task is charged, the toolId / usageAmount returned by options() are used to charge it.
The /options preflight endpoint
The public POST /workflows/<name>/options endpoint lets clients discover a task's options — most notably the credit cost — without submitting it.
enablePreflight controls whether options() runs to resolve options dynamically. When true, its returned toolId / usageAmount drive the response; when false, the response falls back to the workflow's static monetization (or {} if none).
The request body is the same payload the workflow accepts. Optional headers: x-user-subscription-tier, x-config-id, user-id.
Response:
{
"status": "success",
"response": {
"monetization": { "toolId": "premium_tool_id" },
"usageAmount": 3,
"credits": 30
}
}
credits is computed from the resolved toolId and usageAmount (defaulting to 1) using the configured credit price for that tool.
Baggage
options() runs before the task and often does work to resolve pricing. Baggage lets you hand the result of that work to processTask, so it can be reused instead of computed twice.
Put the data you want to reuse in TaskOptions.baggage, then read it back in processTask with flow.meta().baggage():
@Executor("example-task")
export class ExampleExecutor implements TaskExecutor {
async options(cmd: ExampleCommand, flow: OptionsFlow): Promise<TaskOptions> {
const plan = buildRenderPlan(cmd); // work that also drives pricing
return {
monetization: { toolId: 'example_tool' },
usageAmount: plan.steps.length,
baggage: { plan }, // computed once during preflight
};
}
async processTask(cmd: ExampleCommand, _metadata: TaskMetadata, flow: Flow) {
const plan = flow.meta().baggage<RenderPlan>('plan'); // a single typed value
const all = flow.meta().baggage(); // or the whole object
// reuse `plan` instead of rebuilding it
}
}