Skip to main content

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. pick toolId based on selected model).
  • flow.fileMetadata(params) — invokes the v1/files/metadata task to inspect a file before deciding cost.
  • flow.faceDetection(params) — invokes INSTANT_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 } };
}
note

To enable the above implementation, make sure the task has enablePreflight set to true.

note

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
}
}