Skip to main content

objectiveai_sdk/functions/
task.rs

1//! Task types for Function definitions.
2//!
3//! Tasks are the building blocks of Functions. Each task either calls another
4//! Function or runs a Vector Completion. Tasks can be conditionally skipped
5//! or mapped over arrays of inputs.
6//!
7//! # Output Expressions
8//!
9//! Each task has an `output` expression that transforms its raw result into a
10//! [`TaskOutputOwned`](super::expression::TaskOutputOwned). The expression receives
11//! an `output` parameter that is one of four variants:
12//!
13//! - `Scalar(Decimal)` - a single score
14//! - `Vector(Vec<Decimal>)` - a vector of scores
15//! - `Vectors(Vec<Vec<Decimal>>)` - multiple vectors (from mapped tasks)
16//! - `Err(Value)` - an error
17//!
18//! The expression must return a `TaskOutputOwned` valid for the parent function's type:
19//! - **Scalar functions**: must return `Scalar(value)` where value is in [0, 1]
20//! - **Vector functions**: must return `Vector(values)` where values sum to ~1 and match the expected length
21//!
22//! # Output Aggregation
23//!
24//! The function's final output is computed as a **weighted average** of all task outputs
25//! using profile weights. If a function has only one task, that task's output becomes
26//! the function's output directly (with weight 1.0).
27
28use crate::agent;
29use schemars::JsonSchema;
30use serde::{Deserialize, Serialize};
31
32/// A task definition with expressions (pre-compilation).
33///
34/// Task expressions contain dynamic fields (JMESPath or Starlark) that are
35/// resolved against input data during compilation. Use [`compile`](Self::compile)
36/// to produce a concrete [`Task`].
37#[derive(
38    Debug,
39    Clone,
40    PartialEq,
41    Serialize,
42    Deserialize,
43    JsonSchema,
44    arbitrary::Arbitrary,
45)]
46#[serde(tag = "type")]
47#[schemars(rename = "functions.TaskExpression")]
48pub enum TaskExpression {
49    #[schemars(title = "ScalarFunction")]
50    #[serde(rename = "scalar.function")]
51    ScalarFunction(ScalarFunctionTaskExpression),
52    #[schemars(title = "VectorFunction")]
53    #[serde(rename = "vector.function")]
54    VectorFunction(VectorFunctionTaskExpression),
55    #[schemars(title = "VectorCompletion")]
56    #[serde(rename = "vector.completion")]
57    VectorCompletion(VectorCompletionTaskExpression),
58    #[schemars(title = "PlaceholderScalarFunction")]
59    #[serde(rename = "placeholder.scalar.function")]
60    PlaceholderScalarFunction(PlaceholderScalarFunctionTaskExpression),
61    #[schemars(title = "PlaceholderVectorFunction")]
62    #[serde(rename = "placeholder.vector.function")]
63    PlaceholderVectorFunction(PlaceholderVectorFunctionTaskExpression),
64}
65
66impl TaskExpression {
67    pub fn url(&self) -> Option<String> {
68        match self {
69            TaskExpression::ScalarFunction(task) => Some(task.url()),
70            TaskExpression::VectorFunction(task) => Some(task.url()),
71            TaskExpression::VectorCompletion(_) => None,
72            TaskExpression::PlaceholderScalarFunction(_) => None,
73            TaskExpression::PlaceholderVectorFunction(_) => None,
74        }
75    }
76
77    /// Takes and returns the skip expression, if present.
78    pub fn take_skip(&mut self) -> Option<super::expression::Expression> {
79        match self {
80            TaskExpression::ScalarFunction(task) => task.skip.take(),
81            TaskExpression::VectorFunction(task) => task.skip.take(),
82            TaskExpression::VectorCompletion(task) => task.skip.take(),
83            TaskExpression::PlaceholderScalarFunction(task) => task.skip.take(),
84            TaskExpression::PlaceholderVectorFunction(task) => task.skip.take(),
85        }
86    }
87
88    /// Returns the map expression, if this is a mapped task.
89    pub fn map(&self) -> Option<&super::expression::Expression> {
90        match self {
91            TaskExpression::ScalarFunction(task) => task.map.as_ref(),
92            TaskExpression::VectorFunction(task) => task.map.as_ref(),
93            TaskExpression::VectorCompletion(task) => task.map.as_ref(),
94            TaskExpression::PlaceholderScalarFunction(task) => {
95                task.map.as_ref()
96            }
97            TaskExpression::PlaceholderVectorFunction(task) => {
98                task.map.as_ref()
99            }
100        }
101    }
102
103    /// Compiles the expression into a concrete [`Task`].
104    pub fn compile(
105        self,
106        params: &super::expression::Params,
107    ) -> Result<Task, super::expression::ExpressionError> {
108        match self {
109            TaskExpression::ScalarFunction(task) => {
110                task.compile(params).map(Task::ScalarFunction)
111            }
112            TaskExpression::VectorFunction(task) => {
113                task.compile(params).map(Task::VectorFunction)
114            }
115            TaskExpression::VectorCompletion(task) => {
116                task.compile(params).map(Task::VectorCompletion)
117            }
118            TaskExpression::PlaceholderScalarFunction(task) => {
119                task.compile(params).map(Task::PlaceholderScalarFunction)
120            }
121            TaskExpression::PlaceholderVectorFunction(task) => {
122                task.compile(params).map(Task::PlaceholderVectorFunction)
123            }
124        }
125    }
126}
127
128/// A compiled task ready for execution.
129///
130/// Produced by compiling a [`TaskExpression`] against input data. All
131/// expressions have been resolved to concrete values.
132#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
133#[serde(tag = "type")]
134#[schemars(rename = "functions.Task")]
135pub enum Task {
136    /// Calls a scalar function (produces a single score).
137    #[schemars(title = "ScalarFunction")]
138    #[serde(rename = "scalar.function")]
139    ScalarFunction(ScalarFunctionTask),
140    /// Calls a vector function (produces a vector of scores).
141    #[schemars(title = "VectorFunction")]
142    #[serde(rename = "vector.function")]
143    VectorFunction(VectorFunctionTask),
144    /// Runs a vector completion.
145    #[schemars(title = "VectorCompletion")]
146    #[serde(rename = "vector.completion")]
147    VectorCompletion(VectorCompletionTask),
148    /// Placeholder scalar function (always outputs 0.5).
149    #[schemars(title = "PlaceholderScalarFunction")]
150    #[serde(rename = "placeholder.scalar.function")]
151    PlaceholderScalarFunction(PlaceholderScalarFunctionTask),
152    /// Placeholder vector function (always outputs equalized vector).
153    #[schemars(title = "PlaceholderVectorFunction")]
154    #[serde(rename = "placeholder.vector.function")]
155    PlaceholderVectorFunction(PlaceholderVectorFunctionTask),
156}
157
158impl Task {
159    pub fn compile_output(
160        &self,
161        input: &super::expression::InputValue,
162        raw_output: super::expression::TaskOutput,
163    ) -> Result<
164        super::expression::TaskOutputOwned,
165        super::expression::ExpressionError,
166    > {
167        match self {
168            Task::ScalarFunction(task) => {
169                task.compile_output(input, raw_output)
170            }
171            Task::VectorFunction(task) => {
172                task.compile_output(input, raw_output)
173            }
174            Task::VectorCompletion(task) => {
175                task.compile_output(input, raw_output)
176            }
177            Task::PlaceholderScalarFunction(task) => {
178                task.compile_output(input, raw_output)
179            }
180            Task::PlaceholderVectorFunction(task) => {
181                task.compile_output(input, raw_output)
182            }
183        }
184    }
185}
186
187/// Expression for a task that calls a scalar function (pre-compilation).
188#[derive(
189    Debug,
190    Clone,
191    PartialEq,
192    Serialize,
193    Deserialize,
194    JsonSchema,
195    arbitrary::Arbitrary,
196)]
197#[schemars(rename = "functions.ScalarFunctionTaskExpression")]
198pub struct ScalarFunctionTaskExpression {
199    #[serde(flatten)]
200    #[schemars(schema_with = "crate::flatten_schema::<crate::RemotePath>")]
201    pub path: crate::RemotePath,
202
203    /// If this expression evaluates to true, skip the task. Receives: `input`.
204    #[serde(skip_serializing_if = "Option::is_none")]
205    #[schemars(extend("omitempty" = true))]
206    pub skip: Option<super::expression::Expression>,
207
208    /// Expression that evaluates to the number of mapped task instances.
209    /// Each instance receives `map` as an integer index (0-based).
210    #[serde(skip_serializing_if = "Option::is_none")]
211    #[schemars(extend("omitempty" = true))]
212    pub map: Option<super::expression::Expression>,
213
214    /// Expression for the input to pass to the function.
215    /// Receives: `input`, `map` (if mapped).
216    pub input: super::expression::WithExpression<
217        super::expression::InputValueExpression,
218    >,
219
220    /// Expression to transform the task result into a valid function output.
221    ///
222    /// Receives `output` which is one of 4 variants:
223    /// - `Scalar(Decimal)` - a single score
224    /// - `Vector(Vec<Decimal>)` - a vector of scores
225    /// - `Vectors(Vec<Vec<Decimal>>)` - multiple vectors (from mapped tasks)
226    /// - `Err(Value)` - an error
227    ///
228    /// The expression must return a `TaskOutputOwned` that is valid for the parent function's type:
229    /// - For scalar functions: must return `Scalar(value)` where value is in [0, 1]
230    /// - For vector functions: must return `Vector(values)` where values sum to ~1 and match the expected length
231    ///
232    /// The function's final output is computed as a weighted average of all task outputs using
233    /// profile weights. If a function has only one task, that task's output becomes the function's
234    /// output directly.
235    pub output: super::expression::Expression,
236}
237
238impl ScalarFunctionTaskExpression {
239    pub fn url(&self) -> String {
240        self.path.url()
241    }
242
243    /// Compiles the expression into a concrete [`ScalarFunctionTask`].
244    pub fn compile(
245        self,
246        params: &super::expression::Params,
247    ) -> Result<ScalarFunctionTask, super::expression::ExpressionError> {
248        let input = self.input.compile_one(params)?.compile(params)?;
249        Ok(ScalarFunctionTask {
250            path: self.path,
251            input,
252            output: self.output,
253        })
254    }
255}
256
257/// A compiled scalar function task ready for execution.
258#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
259#[schemars(rename = "functions.ScalarFunctionTask")]
260pub struct ScalarFunctionTask {
261    #[serde(flatten)]
262    #[schemars(schema_with = "crate::flatten_schema::<crate::RemotePath>")]
263    pub path: crate::RemotePath,
264    /// The resolved input to pass to the function.
265    pub input: super::expression::InputValue,
266    /// Expression to transform the task result into a valid function output.
267    ///
268    /// Receives `output` as the nested function's result (Scalar or Vector).
269    /// Must return a `TaskOutputOwned` valid for the parent function's type (scalar or vector).
270    /// See [`ScalarFunctionTaskExpression::output`] for full documentation.
271    pub output: super::expression::Expression,
272}
273
274impl ScalarFunctionTask {
275    pub fn url(&self) -> String {
276        self.path.url()
277    }
278
279    pub fn compile_output(
280        &self,
281        input: &super::expression::InputValue,
282        raw_output: super::expression::TaskOutput,
283    ) -> Result<
284        super::expression::TaskOutputOwned,
285        super::expression::ExpressionError,
286    > {
287        let params =
288            super::expression::Params::Ref(super::expression::ParamsRef {
289                input,
290                output: Some(raw_output),
291                map: None,
292            });
293        let compiled_output = self.output.compile_one(&params)?;
294        Ok(compiled_output)
295    }
296}
297
298/// Expression for a task that calls a vector function (pre-compilation).
299#[derive(
300    Debug,
301    Clone,
302    PartialEq,
303    Serialize,
304    Deserialize,
305    JsonSchema,
306    arbitrary::Arbitrary,
307)]
308#[schemars(rename = "functions.VectorFunctionTaskExpression")]
309pub struct VectorFunctionTaskExpression {
310    #[serde(flatten)]
311    #[schemars(schema_with = "crate::flatten_schema::<crate::RemotePath>")]
312    pub path: crate::RemotePath,
313
314    /// If this expression evaluates to true, skip the task. Receives: `input`.
315    #[serde(skip_serializing_if = "Option::is_none")]
316    #[schemars(extend("omitempty" = true))]
317    pub skip: Option<super::expression::Expression>,
318
319    /// Expression that evaluates to the number of mapped task instances.
320    /// Each instance receives `map` as an integer index (0-based).
321    #[serde(skip_serializing_if = "Option::is_none")]
322    #[schemars(extend("omitempty" = true))]
323    pub map: Option<super::expression::Expression>,
324
325    /// Expression for the input to pass to the function.
326    /// Receives: `input`, `map` (if mapped).
327    pub input: super::expression::WithExpression<
328        super::expression::InputValueExpression,
329    >,
330
331    /// Expression to transform the task result into a valid function output.
332    ///
333    /// Receives `output` which is one of 4 variants:
334    /// - `Scalar(Decimal)` - a single score
335    /// - `Vector(Vec<Decimal>)` - a vector of scores
336    /// - `Vectors(Vec<Vec<Decimal>>)` - multiple vectors (from mapped tasks)
337    /// - `Err(Value)` - an error
338    ///
339    /// The expression must return a `TaskOutputOwned` that is valid for the parent function's type:
340    /// - For scalar functions: must return `Scalar(value)` where value is in [0, 1]
341    /// - For vector functions: must return `Vector(values)` where values sum to ~1 and match the expected length
342    ///
343    /// The function's final output is computed as a weighted average of all task outputs using
344    /// profile weights. If a function has only one task, that task's output becomes the function's
345    /// output directly.
346    pub output: super::expression::Expression,
347}
348
349impl VectorFunctionTaskExpression {
350    pub fn url(&self) -> String {
351        self.path.url()
352    }
353
354    /// Compiles the expression into a concrete [`VectorFunctionTask`].
355    pub fn compile(
356        self,
357        params: &super::expression::Params,
358    ) -> Result<VectorFunctionTask, super::expression::ExpressionError> {
359        let input = self.input.compile_one(params)?.compile(params)?;
360        Ok(VectorFunctionTask {
361            path: self.path,
362            input,
363            output: self.output,
364        })
365    }
366}
367
368/// A compiled vector function task ready for execution.
369#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
370#[schemars(rename = "functions.VectorFunctionTask")]
371pub struct VectorFunctionTask {
372    #[serde(flatten)]
373    #[schemars(schema_with = "crate::flatten_schema::<crate::RemotePath>")]
374    pub path: crate::RemotePath,
375    /// The resolved input to pass to the function.
376    pub input: super::expression::InputValue,
377    /// Expression to transform the task result into a valid function output.
378    ///
379    /// Receives `output` as the nested function's result (Scalar or Vector).
380    /// Must return a `TaskOutputOwned` valid for the parent function's type (scalar or vector).
381    /// See [`VectorFunctionTaskExpression::output`] for full documentation.
382    pub output: super::expression::Expression,
383}
384
385impl VectorFunctionTask {
386    pub fn url(&self) -> String {
387        self.path.url()
388    }
389
390    pub fn compile_output(
391        &self,
392        input: &super::expression::InputValue,
393        raw_output: super::expression::TaskOutput,
394    ) -> Result<
395        super::expression::TaskOutputOwned,
396        super::expression::ExpressionError,
397    > {
398        let params =
399            super::expression::Params::Ref(super::expression::ParamsRef {
400                input,
401                output: Some(raw_output),
402                map: None,
403            });
404        let compiled_output = self.output.compile_one(&params)?;
405        Ok(compiled_output)
406    }
407}
408
409/// Expression for a task that runs a vector completion (pre-compilation).
410#[derive(
411    Debug,
412    Clone,
413    PartialEq,
414    Serialize,
415    Deserialize,
416    JsonSchema,
417    arbitrary::Arbitrary,
418)]
419#[schemars(rename = "functions.VectorCompletionTaskExpression")]
420pub struct VectorCompletionTaskExpression {
421    /// If this expression evaluates to true, skip the task. Receives: `input`.
422    #[serde(skip_serializing_if = "Option::is_none")]
423    #[schemars(extend("omitempty" = true))]
424    pub skip: Option<super::expression::Expression>,
425
426    /// Expression that evaluates to the number of mapped task instances.
427    /// Each instance receives `map` as an integer index (0-based).
428    #[serde(skip_serializing_if = "Option::is_none")]
429    #[schemars(extend("omitempty" = true))]
430    pub map: Option<super::expression::Expression>,
431
432    /// Expression for the conversation messages (the prompt).
433    /// Receives: `input`, `map` (if mapped).
434    pub messages: super::expression::WithExpression<
435        Vec<
436            super::expression::WithExpression<
437                agent::completions::message::MessageExpression,
438            >,
439        >,
440    >,
441    /// Expression for the possible responses the LLMs can vote for.
442    /// Receives: `input`, `map` (if mapped).
443    pub responses: super::expression::WithExpression<
444        Vec<
445            super::expression::WithExpression<
446                agent::completions::message::RichContentExpression,
447            >,
448        >,
449    >,
450
451    /// Expression to transform the task result into a valid function output.
452    ///
453    /// Receives `output` as the task's raw result (typically `Vector(scores)`).
454    ///
455    /// The expression must return a `TaskOutputOwned` that is valid for the parent function's type:
456    /// - For scalar functions: must return `Scalar(value)` where value is in [0, 1]
457    /// - For vector functions: must return `Vector(values)` where values sum to ~1 and match the expected length
458    ///
459    /// The function's final output is computed as a weighted average of all task outputs using
460    /// profile weights. If a function has only one task, that task's output becomes the function's
461    /// output directly.
462    pub output: super::expression::Expression,
463}
464
465impl VectorCompletionTaskExpression {
466    /// Compiles the expression into a concrete [`VectorCompletionTask`].
467    pub fn compile(
468        self,
469        params: &super::expression::Params,
470    ) -> Result<VectorCompletionTask, super::expression::ExpressionError> {
471        // compile messages
472        let messages = self.messages.compile_one(params)?;
473        let mut compiled_messages = Vec::with_capacity(messages.len());
474        for message in messages {
475            match message.compile_one_or_many(params)? {
476                super::expression::OneOrMany::One(one_message) => {
477                    compiled_messages.push(one_message.compile(params)?);
478                }
479                super::expression::OneOrMany::Many(many_messages) => {
480                    for message in many_messages {
481                        compiled_messages.push(message.compile(params)?);
482                    }
483                }
484            }
485        }
486
487        // compile responses
488        let responses = self.responses.compile_one(params)?;
489        let mut compiled_responses = Vec::with_capacity(responses.len());
490        for response in responses {
491            match response.compile_one_or_many(params)? {
492                super::expression::OneOrMany::One(one_response) => {
493                    compiled_responses.push(one_response.compile(params)?);
494                }
495                super::expression::OneOrMany::Many(many_responses) => {
496                    for response in many_responses {
497                        compiled_responses.push(response.compile(params)?);
498                    }
499                }
500            }
501        }
502
503        Ok(VectorCompletionTask {
504            messages: compiled_messages,
505            responses: compiled_responses,
506            output: self.output,
507        })
508    }
509}
510
511/// A compiled vector completion task ready for execution.
512#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
513#[schemars(rename = "functions.VectorCompletionTask")]
514pub struct VectorCompletionTask {
515    /// The resolved conversation messages.
516    pub messages: Vec<agent::completions::message::Message>,
517    /// The resolved response options the LLMs can vote for.
518    pub responses: Vec<agent::completions::message::RichContent>,
519    /// Expression to transform the task result into a valid function output.
520    ///
521    /// Receives `output` as the task's raw result (typically `Vector(scores)`).
522    /// Must return a `TaskOutputOwned` valid for the parent function's type (scalar or vector).
523    /// See [`VectorCompletionTaskExpression::output`] for full documentation.
524    pub output: super::expression::Expression,
525}
526
527impl VectorCompletionTask {
528    pub fn compile_output(
529        &self,
530        input: &super::expression::InputValue,
531        raw_output: super::expression::TaskOutput,
532    ) -> Result<
533        super::expression::TaskOutputOwned,
534        super::expression::ExpressionError,
535    > {
536        let params =
537            super::expression::Params::Ref(super::expression::ParamsRef {
538                input,
539                output: Some(raw_output),
540                map: None,
541            });
542        let compiled_output = self.output.compile_one(&params)?;
543        Ok(compiled_output)
544    }
545}
546
547/// Expression for a placeholder scalar function task (pre-compilation).
548///
549/// Like [`ScalarFunctionTaskExpression`] but without owner/repository/commit.
550/// Always produces a fixed output of 0.5.
551#[derive(
552    Debug,
553    Clone,
554    PartialEq,
555    Serialize,
556    Deserialize,
557    JsonSchema,
558    arbitrary::Arbitrary,
559)]
560#[schemars(rename = "functions.PlaceholderScalarFunctionTaskExpression")]
561pub struct PlaceholderScalarFunctionTaskExpression {
562    /// JSON Schema defining the expected input structure.
563    pub input_schema: super::expression::InputSchema,
564
565    /// If this expression evaluates to true, skip the task. Receives: `input`.
566    #[serde(skip_serializing_if = "Option::is_none")]
567    #[schemars(extend("omitempty" = true))]
568    pub skip: Option<super::expression::Expression>,
569
570    /// Expression that evaluates to the number of mapped task instances.
571    /// Each instance receives `map` as an integer index (0-based).
572    #[serde(skip_serializing_if = "Option::is_none")]
573    #[schemars(extend("omitempty" = true))]
574    pub map: Option<super::expression::Expression>,
575
576    /// Expression for the input to pass to the placeholder function.
577    /// Receives: `input`, `map` (if mapped).
578    pub input: super::expression::WithExpression<
579        super::expression::InputValueExpression,
580    >,
581
582    /// Expression to transform the fixed 0.5 output.
583    /// Receives: `input`, `output` as `Scalar(0.5)`.
584    pub output: super::expression::Expression,
585}
586
587impl PlaceholderScalarFunctionTaskExpression {
588    pub fn compile(
589        self,
590        params: &super::expression::Params,
591    ) -> Result<PlaceholderScalarFunctionTask, super::expression::ExpressionError>
592    {
593        let input = self.input.compile_one(params)?.compile(params)?;
594        Ok(PlaceholderScalarFunctionTask {
595            input_schema: self.input_schema,
596            input,
597            output: self.output,
598        })
599    }
600}
601
602/// A compiled placeholder scalar function task.
603///
604/// Always produces `Scalar(0.5)` before the output expression
605/// is applied.
606#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
607#[schemars(rename = "functions.PlaceholderScalarFunctionTask")]
608pub struct PlaceholderScalarFunctionTask {
609    /// JSON Schema defining the expected input structure.
610    pub input_schema: super::expression::InputSchema,
611    /// The resolved input.
612    pub input: super::expression::InputValue,
613    /// Expression to transform the fixed 0.5 output.
614    pub output: super::expression::Expression,
615}
616
617impl PlaceholderScalarFunctionTask {
618    pub fn compile_output(
619        &self,
620        input: &super::expression::InputValue,
621        raw_output: super::expression::TaskOutput,
622    ) -> Result<
623        super::expression::TaskOutputOwned,
624        super::expression::ExpressionError,
625    > {
626        let params =
627            super::expression::Params::Ref(super::expression::ParamsRef {
628                input,
629                output: Some(raw_output),
630                map: None,
631            });
632        let compiled_output = self.output.compile_one(&params)?;
633        Ok(compiled_output)
634    }
635}
636
637/// Expression for a placeholder vector function task (pre-compilation).
638///
639/// Like [`VectorFunctionTaskExpression`] but without owner/repository/commit.
640/// Always produces an equalized vector of length `output_length`.
641#[derive(
642    Debug,
643    Clone,
644    PartialEq,
645    Serialize,
646    Deserialize,
647    JsonSchema,
648    arbitrary::Arbitrary,
649)]
650#[schemars(rename = "functions.PlaceholderVectorFunctionTaskExpression")]
651pub struct PlaceholderVectorFunctionTaskExpression {
652    /// JSON Schema defining the expected input structure.
653    pub input_schema: super::expression::InputSchema,
654
655    /// Expression computing the expected output vector length.
656    /// Receives: `input`.
657    pub output_length: super::expression::Expression,
658
659    /// Expression transforming input into sub-inputs for swiss system.
660    /// Receives: `input`.
661    pub input_split: super::expression::Expression,
662
663    /// Expression merging sub-inputs back into one input.
664    /// Receives: `input` (as an array).
665    pub input_merge: super::expression::Expression,
666
667    /// If this expression evaluates to true, skip the task. Receives: `input`.
668    #[serde(skip_serializing_if = "Option::is_none")]
669    #[schemars(extend("omitempty" = true))]
670    pub skip: Option<super::expression::Expression>,
671
672    /// Expression that evaluates to the number of mapped task instances.
673    /// Each instance receives `map` as an integer index (0-based).
674    #[serde(skip_serializing_if = "Option::is_none")]
675    #[schemars(extend("omitempty" = true))]
676    pub map: Option<super::expression::Expression>,
677
678    /// Expression for the input to pass to the placeholder function.
679    /// Receives: `input`, `map` (if mapped).
680    pub input: super::expression::WithExpression<
681        super::expression::InputValueExpression,
682    >,
683
684    /// Expression to transform the equalized vector output.
685    /// Receives: `input`, `output` as `Vector(equalized)`.
686    pub output: super::expression::Expression,
687}
688
689impl PlaceholderVectorFunctionTaskExpression {
690    pub fn compile(
691        self,
692        params: &super::expression::Params,
693    ) -> Result<PlaceholderVectorFunctionTask, super::expression::ExpressionError>
694    {
695        let input = self.input.compile_one(params)?.compile(params)?;
696        Ok(PlaceholderVectorFunctionTask {
697            input_schema: self.input_schema,
698            output_length: self.output_length,
699            input_split: self.input_split,
700            input_merge: self.input_merge,
701            input,
702            output: self.output,
703        })
704    }
705}
706
707/// A compiled placeholder vector function task.
708///
709/// Always produces `Vector(vec![1/N; output_length])` before
710/// the output expression is applied.
711#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
712#[schemars(rename = "functions.PlaceholderVectorFunctionTask")]
713pub struct PlaceholderVectorFunctionTask {
714    /// JSON Schema defining the expected input structure.
715    pub input_schema: super::expression::InputSchema,
716    /// Expression computing the expected output vector length.
717    pub output_length: super::expression::Expression,
718    /// Expression transforming input into sub-inputs for swiss system.
719    pub input_split: super::expression::Expression,
720    /// Expression merging sub-inputs back into one input.
721    pub input_merge: super::expression::Expression,
722    /// The resolved input.
723    pub input: super::expression::InputValue,
724    /// Expression to transform the equalized vector output.
725    pub output: super::expression::Expression,
726}
727
728impl PlaceholderVectorFunctionTask {
729    pub fn compile_output(
730        &self,
731        input: &super::expression::InputValue,
732        raw_output: super::expression::TaskOutput,
733    ) -> Result<
734        super::expression::TaskOutputOwned,
735        super::expression::ExpressionError,
736    > {
737        let params =
738            super::expression::Params::Ref(super::expression::ParamsRef {
739                input,
740                output: Some(raw_output),
741                map: None,
742            });
743        let compiled_output = self.output.compile_one(&params)?;
744        Ok(compiled_output)
745    }
746}
747
748/// The result of compiling a task expression.
749///
750/// Tasks without a `map` field compile to a single task. Tasks with a `map`
751/// expression are expanded into multiple tasks, one per integer index from
752/// 0 to the evaluated count.
753#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
754#[serde(untagged)]
755#[schemars(rename = "functions.CompiledTask")]
756pub enum CompiledTask {
757    /// A single task (no mapping).
758    #[schemars(title = "One")]
759    One(Task),
760    /// Multiple task instances from mapped execution.
761    #[schemars(title = "Many")]
762    Many(Vec<Task>),
763}