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                tasks_min: None,
293                tasks_max: None,
294                depth: None,
295                name: None,
296                spec: None,
297            });
298        let compiled_output = self.output.compile_one(&params)?;
299        Ok(compiled_output)
300    }
301}
302
303/// Expression for a task that calls a vector function (pre-compilation).
304#[derive(
305    Debug,
306    Clone,
307    PartialEq,
308    Serialize,
309    Deserialize,
310    JsonSchema,
311    arbitrary::Arbitrary,
312)]
313#[schemars(rename = "functions.VectorFunctionTaskExpression")]
314pub struct VectorFunctionTaskExpression {
315    #[serde(flatten)]
316    #[schemars(schema_with = "crate::flatten_schema::<crate::RemotePath>")]
317    pub path: crate::RemotePath,
318
319    /// If this expression evaluates to true, skip the task. Receives: `input`.
320    #[serde(skip_serializing_if = "Option::is_none")]
321    #[schemars(extend("omitempty" = true))]
322    pub skip: Option<super::expression::Expression>,
323
324    /// Expression that evaluates to the number of mapped task instances.
325    /// Each instance receives `map` as an integer index (0-based).
326    #[serde(skip_serializing_if = "Option::is_none")]
327    #[schemars(extend("omitempty" = true))]
328    pub map: Option<super::expression::Expression>,
329
330    /// Expression for the input to pass to the function.
331    /// Receives: `input`, `map` (if mapped).
332    pub input: super::expression::WithExpression<
333        super::expression::InputValueExpression,
334    >,
335
336    /// Expression to transform the task result into a valid function output.
337    ///
338    /// Receives `output` which is one of 4 variants:
339    /// - `Scalar(Decimal)` - a single score
340    /// - `Vector(Vec<Decimal>)` - a vector of scores
341    /// - `Vectors(Vec<Vec<Decimal>>)` - multiple vectors (from mapped tasks)
342    /// - `Err(Value)` - an error
343    ///
344    /// The expression must return a `TaskOutputOwned` that is valid for the parent function's type:
345    /// - For scalar functions: must return `Scalar(value)` where value is in [0, 1]
346    /// - For vector functions: must return `Vector(values)` where values sum to ~1 and match the expected length
347    ///
348    /// The function's final output is computed as a weighted average of all task outputs using
349    /// profile weights. If a function has only one task, that task's output becomes the function's
350    /// output directly.
351    pub output: super::expression::Expression,
352}
353
354impl VectorFunctionTaskExpression {
355    pub fn url(&self) -> String {
356        self.path.url()
357    }
358
359    /// Compiles the expression into a concrete [`VectorFunctionTask`].
360    pub fn compile(
361        self,
362        params: &super::expression::Params,
363    ) -> Result<VectorFunctionTask, super::expression::ExpressionError> {
364        let input = self.input.compile_one(params)?.compile(params)?;
365        Ok(VectorFunctionTask {
366            path: self.path,
367            input,
368            output: self.output,
369        })
370    }
371}
372
373/// A compiled vector function task ready for execution.
374#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
375#[schemars(rename = "functions.VectorFunctionTask")]
376pub struct VectorFunctionTask {
377    #[serde(flatten)]
378    #[schemars(schema_with = "crate::flatten_schema::<crate::RemotePath>")]
379    pub path: crate::RemotePath,
380    /// The resolved input to pass to the function.
381    pub input: super::expression::InputValue,
382    /// Expression to transform the task result into a valid function output.
383    ///
384    /// Receives `output` as the nested function's result (Scalar or Vector).
385    /// Must return a `TaskOutputOwned` valid for the parent function's type (scalar or vector).
386    /// See [`VectorFunctionTaskExpression::output`] for full documentation.
387    pub output: super::expression::Expression,
388}
389
390impl VectorFunctionTask {
391    pub fn url(&self) -> String {
392        self.path.url()
393    }
394
395    pub fn compile_output(
396        &self,
397        input: &super::expression::InputValue,
398        raw_output: super::expression::TaskOutput,
399    ) -> Result<
400        super::expression::TaskOutputOwned,
401        super::expression::ExpressionError,
402    > {
403        let params =
404            super::expression::Params::Ref(super::expression::ParamsRef {
405                input,
406                output: Some(raw_output),
407                map: None,
408                tasks_min: None,
409                tasks_max: None,
410                depth: None,
411                name: None,
412                spec: None,
413            });
414        let compiled_output = self.output.compile_one(&params)?;
415        Ok(compiled_output)
416    }
417}
418
419/// Expression for a task that runs a vector completion (pre-compilation).
420#[derive(
421    Debug,
422    Clone,
423    PartialEq,
424    Serialize,
425    Deserialize,
426    JsonSchema,
427    arbitrary::Arbitrary,
428)]
429#[schemars(rename = "functions.VectorCompletionTaskExpression")]
430pub struct VectorCompletionTaskExpression {
431    /// If this expression evaluates to true, skip the task. Receives: `input`.
432    #[serde(skip_serializing_if = "Option::is_none")]
433    #[schemars(extend("omitempty" = true))]
434    pub skip: Option<super::expression::Expression>,
435
436    /// Expression that evaluates to the number of mapped task instances.
437    /// Each instance receives `map` as an integer index (0-based).
438    #[serde(skip_serializing_if = "Option::is_none")]
439    #[schemars(extend("omitempty" = true))]
440    pub map: Option<super::expression::Expression>,
441
442    /// Expression for the conversation messages (the prompt).
443    /// Receives: `input`, `map` (if mapped).
444    pub messages: super::expression::WithExpression<
445        Vec<
446            super::expression::WithExpression<
447                agent::completions::message::MessageExpression,
448            >,
449        >,
450    >,
451    /// Expression for the possible responses the LLMs can vote for.
452    /// Receives: `input`, `map` (if mapped).
453    pub responses: super::expression::WithExpression<
454        Vec<
455            super::expression::WithExpression<
456                agent::completions::message::RichContentExpression,
457            >,
458        >,
459    >,
460
461    /// Expression to transform the task result into a valid function output.
462    ///
463    /// Receives `output` as the task's raw result (typically `Vector(scores)`).
464    ///
465    /// The expression must return a `TaskOutputOwned` that is valid for the parent function's type:
466    /// - For scalar functions: must return `Scalar(value)` where value is in [0, 1]
467    /// - For vector functions: must return `Vector(values)` where values sum to ~1 and match the expected length
468    ///
469    /// The function's final output is computed as a weighted average of all task outputs using
470    /// profile weights. If a function has only one task, that task's output becomes the function's
471    /// output directly.
472    pub output: super::expression::Expression,
473}
474
475impl VectorCompletionTaskExpression {
476    /// Compiles the expression into a concrete [`VectorCompletionTask`].
477    pub fn compile(
478        self,
479        params: &super::expression::Params,
480    ) -> Result<VectorCompletionTask, super::expression::ExpressionError> {
481        // compile messages
482        let messages = self.messages.compile_one(params)?;
483        let mut compiled_messages = Vec::with_capacity(messages.len());
484        for message in messages {
485            match message.compile_one_or_many(params)? {
486                super::expression::OneOrMany::One(one_message) => {
487                    compiled_messages.push(one_message.compile(params)?);
488                }
489                super::expression::OneOrMany::Many(many_messages) => {
490                    for message in many_messages {
491                        compiled_messages.push(message.compile(params)?);
492                    }
493                }
494            }
495        }
496
497        // compile responses
498        let responses = self.responses.compile_one(params)?;
499        let mut compiled_responses = Vec::with_capacity(responses.len());
500        for response in responses {
501            match response.compile_one_or_many(params)? {
502                super::expression::OneOrMany::One(one_response) => {
503                    compiled_responses.push(one_response.compile(params)?);
504                }
505                super::expression::OneOrMany::Many(many_responses) => {
506                    for response in many_responses {
507                        compiled_responses.push(response.compile(params)?);
508                    }
509                }
510            }
511        }
512
513        Ok(VectorCompletionTask {
514            messages: compiled_messages,
515            responses: compiled_responses,
516            output: self.output,
517        })
518    }
519}
520
521/// A compiled vector completion task ready for execution.
522#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
523#[schemars(rename = "functions.VectorCompletionTask")]
524pub struct VectorCompletionTask {
525    /// The resolved conversation messages.
526    pub messages: Vec<agent::completions::message::Message>,
527    /// The resolved response options the LLMs can vote for.
528    pub responses: Vec<agent::completions::message::RichContent>,
529    /// Expression to transform the task result into a valid function output.
530    ///
531    /// Receives `output` as the task's raw result (typically `Vector(scores)`).
532    /// Must return a `TaskOutputOwned` valid for the parent function's type (scalar or vector).
533    /// See [`VectorCompletionTaskExpression::output`] for full documentation.
534    pub output: super::expression::Expression,
535}
536
537impl VectorCompletionTask {
538    pub fn compile_output(
539        &self,
540        input: &super::expression::InputValue,
541        raw_output: super::expression::TaskOutput,
542    ) -> Result<
543        super::expression::TaskOutputOwned,
544        super::expression::ExpressionError,
545    > {
546        let params =
547            super::expression::Params::Ref(super::expression::ParamsRef {
548                input,
549                output: Some(raw_output),
550                map: None,
551                tasks_min: None,
552                tasks_max: None,
553                depth: None,
554                name: None,
555                spec: None,
556            });
557        let compiled_output = self.output.compile_one(&params)?;
558        Ok(compiled_output)
559    }
560}
561
562/// Expression for a placeholder scalar function task (pre-compilation).
563///
564/// Like [`ScalarFunctionTaskExpression`] but without owner/repository/commit.
565/// Always produces a fixed output of 0.5.
566#[derive(
567    Debug,
568    Clone,
569    PartialEq,
570    Serialize,
571    Deserialize,
572    JsonSchema,
573    arbitrary::Arbitrary,
574)]
575#[schemars(rename = "functions.PlaceholderScalarFunctionTaskExpression")]
576pub struct PlaceholderScalarFunctionTaskExpression {
577    /// JSON Schema defining the expected input structure.
578    pub input_schema: super::expression::InputSchema,
579
580    /// If this expression evaluates to true, skip the task. Receives: `input`.
581    #[serde(skip_serializing_if = "Option::is_none")]
582    #[schemars(extend("omitempty" = true))]
583    pub skip: Option<super::expression::Expression>,
584
585    /// Expression that evaluates to the number of mapped task instances.
586    /// Each instance receives `map` as an integer index (0-based).
587    #[serde(skip_serializing_if = "Option::is_none")]
588    #[schemars(extend("omitempty" = true))]
589    pub map: Option<super::expression::Expression>,
590
591    /// Expression for the input to pass to the placeholder function.
592    /// Receives: `input`, `map` (if mapped).
593    pub input: super::expression::WithExpression<
594        super::expression::InputValueExpression,
595    >,
596
597    /// Expression to transform the fixed 0.5 output.
598    /// Receives: `input`, `output` as `Scalar(0.5)`.
599    pub output: super::expression::Expression,
600}
601
602impl PlaceholderScalarFunctionTaskExpression {
603    pub fn compile(
604        self,
605        params: &super::expression::Params,
606    ) -> Result<PlaceholderScalarFunctionTask, super::expression::ExpressionError>
607    {
608        let input = self.input.compile_one(params)?.compile(params)?;
609        Ok(PlaceholderScalarFunctionTask {
610            input_schema: self.input_schema,
611            input,
612            output: self.output,
613        })
614    }
615}
616
617/// A compiled placeholder scalar function task.
618///
619/// Always produces `Scalar(0.5)` before the output expression
620/// is applied.
621#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
622#[schemars(rename = "functions.PlaceholderScalarFunctionTask")]
623pub struct PlaceholderScalarFunctionTask {
624    /// JSON Schema defining the expected input structure.
625    pub input_schema: super::expression::InputSchema,
626    /// The resolved input.
627    pub input: super::expression::InputValue,
628    /// Expression to transform the fixed 0.5 output.
629    pub output: super::expression::Expression,
630}
631
632impl PlaceholderScalarFunctionTask {
633    pub fn compile_output(
634        &self,
635        input: &super::expression::InputValue,
636        raw_output: super::expression::TaskOutput,
637    ) -> Result<
638        super::expression::TaskOutputOwned,
639        super::expression::ExpressionError,
640    > {
641        let params =
642            super::expression::Params::Ref(super::expression::ParamsRef {
643                input,
644                output: Some(raw_output),
645                map: None,
646                tasks_min: None,
647                tasks_max: None,
648                depth: None,
649                name: None,
650                spec: None,
651            });
652        let compiled_output = self.output.compile_one(&params)?;
653        Ok(compiled_output)
654    }
655}
656
657/// Expression for a placeholder vector function task (pre-compilation).
658///
659/// Like [`VectorFunctionTaskExpression`] but without owner/repository/commit.
660/// Always produces an equalized vector of length `output_length`.
661#[derive(
662    Debug,
663    Clone,
664    PartialEq,
665    Serialize,
666    Deserialize,
667    JsonSchema,
668    arbitrary::Arbitrary,
669)]
670#[schemars(rename = "functions.PlaceholderVectorFunctionTaskExpression")]
671pub struct PlaceholderVectorFunctionTaskExpression {
672    /// JSON Schema defining the expected input structure.
673    pub input_schema: super::expression::InputSchema,
674
675    /// Expression computing the expected output vector length.
676    /// Receives: `input`.
677    pub output_length: super::expression::Expression,
678
679    /// Expression transforming input into sub-inputs for swiss system.
680    /// Receives: `input`.
681    pub input_split: super::expression::Expression,
682
683    /// Expression merging sub-inputs back into one input.
684    /// Receives: `input` (as an array).
685    pub input_merge: super::expression::Expression,
686
687    /// If this expression evaluates to true, skip the task. Receives: `input`.
688    #[serde(skip_serializing_if = "Option::is_none")]
689    #[schemars(extend("omitempty" = true))]
690    pub skip: Option<super::expression::Expression>,
691
692    /// Expression that evaluates to the number of mapped task instances.
693    /// Each instance receives `map` as an integer index (0-based).
694    #[serde(skip_serializing_if = "Option::is_none")]
695    #[schemars(extend("omitempty" = true))]
696    pub map: Option<super::expression::Expression>,
697
698    /// Expression for the input to pass to the placeholder function.
699    /// Receives: `input`, `map` (if mapped).
700    pub input: super::expression::WithExpression<
701        super::expression::InputValueExpression,
702    >,
703
704    /// Expression to transform the equalized vector output.
705    /// Receives: `input`, `output` as `Vector(equalized)`.
706    pub output: super::expression::Expression,
707}
708
709impl PlaceholderVectorFunctionTaskExpression {
710    pub fn compile(
711        self,
712        params: &super::expression::Params,
713    ) -> Result<PlaceholderVectorFunctionTask, super::expression::ExpressionError>
714    {
715        let input = self.input.compile_one(params)?.compile(params)?;
716        Ok(PlaceholderVectorFunctionTask {
717            input_schema: self.input_schema,
718            output_length: self.output_length,
719            input_split: self.input_split,
720            input_merge: self.input_merge,
721            input,
722            output: self.output,
723        })
724    }
725}
726
727/// A compiled placeholder vector function task.
728///
729/// Always produces `Vector(vec![1/N; output_length])` before
730/// the output expression is applied.
731#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
732#[schemars(rename = "functions.PlaceholderVectorFunctionTask")]
733pub struct PlaceholderVectorFunctionTask {
734    /// JSON Schema defining the expected input structure.
735    pub input_schema: super::expression::InputSchema,
736    /// Expression computing the expected output vector length.
737    pub output_length: super::expression::Expression,
738    /// Expression transforming input into sub-inputs for swiss system.
739    pub input_split: super::expression::Expression,
740    /// Expression merging sub-inputs back into one input.
741    pub input_merge: super::expression::Expression,
742    /// The resolved input.
743    pub input: super::expression::InputValue,
744    /// Expression to transform the equalized vector output.
745    pub output: super::expression::Expression,
746}
747
748impl PlaceholderVectorFunctionTask {
749    pub fn compile_output(
750        &self,
751        input: &super::expression::InputValue,
752        raw_output: super::expression::TaskOutput,
753    ) -> Result<
754        super::expression::TaskOutputOwned,
755        super::expression::ExpressionError,
756    > {
757        let params =
758            super::expression::Params::Ref(super::expression::ParamsRef {
759                input,
760                output: Some(raw_output),
761                map: None,
762                tasks_min: None,
763                tasks_max: None,
764                depth: None,
765                name: None,
766                spec: None,
767            });
768        let compiled_output = self.output.compile_one(&params)?;
769        Ok(compiled_output)
770    }
771}
772
773/// The result of compiling a task expression.
774///
775/// Tasks without a `map` field compile to a single task. Tasks with a `map`
776/// expression are expanded into multiple tasks, one per integer index from
777/// 0 to the evaluated count.
778#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
779#[serde(untagged)]
780#[schemars(rename = "functions.CompiledTask")]
781pub enum CompiledTask {
782    /// A single task (no mapping).
783    #[schemars(title = "One")]
784    One(Task),
785    /// Multiple task instances from mapped execution.
786    #[schemars(title = "Many")]
787    Many(Vec<Task>),
788}