Skip to main content

objectiveai_sdk/functions/executions/response/unary/
function_execution.rs

1//! Complete function execution response.
2
3use crate::{
4    agent, error,
5    functions::{self, executions::response},
6};
7use schemars::JsonSchema;
8use serde::{Deserialize, Serialize};
9
10/// A complete function execution response (non-streaming).
11#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
12#[schemars(rename = "functions.executions.response.unary.FunctionExecution")]
13pub struct FunctionExecution {
14    /// Unique identifier for this execution.
15    pub id: String,
16    /// Results from each task in the function.
17    pub tasks: Vec<super::Task>,
18    /// Whether any tasks encountered errors.
19    pub tasks_errors: bool,
20    /// Reasoning summary if reasoning was enabled.
21    pub reasoning: Option<super::ReasoningSummary>,
22    /// The final output (scalar or vector score).
23    pub output: super::super::Output,
24    /// Error details if the execution failed.
25    pub error: Option<error::ResponseError>,
26    /// Unix timestamp when the execution was created.
27    pub created: u64,
28    /// The function used (if remote).
29    pub function: Option<crate::RemotePath>,
30    /// The profile used (if remote).
31    pub profile: Option<crate::RemotePath>,
32    /// Object type identifier.
33    pub object: super::Object,
34    /// Aggregated token and cost usage.
35    pub usage: agent::completions::response::Usage,
36}
37
38impl FunctionExecution {
39    pub fn any_usage(&self) -> bool {
40        self.usage.any_usage()
41    }
42
43    /// Normalize non-deterministic fields for test snapshot comparison.
44    pub fn normalize_for_tests(&mut self) {
45        self.id = String::new();
46        self.created = 0;
47        for task in &mut self.tasks {
48            match task {
49                super::Task::VectorCompletion(vt) => {
50                    // `index` reflects arrival order from concurrent
51                    // sub-task streams, which is non-deterministic;
52                    // pin it to the local sibling position (the last
53                    // element of `task_path`) for snapshot stability.
54                    vt.index = vt.task_path.last().copied().unwrap_or(0);
55                    vt.inner.normalize_for_tests();
56                }
57                super::Task::FunctionExecution(ft) => {
58                    ft.index = ft.task_path.last().copied().unwrap_or(0);
59                    ft.inner.normalize_for_tests();
60                }
61            }
62        }
63        self.tasks.sort_by_cached_key(|t| t.snapshot_sort_key());
64    }
65}
66
67impl From<response::streaming::FunctionExecutionChunk> for FunctionExecution {
68    fn from(
69        response::streaming::FunctionExecutionChunk {
70            id,
71            tasks,
72            tasks_errors,
73            reasoning,
74            output,
75            error,
76            created,
77            function,
78            profile,
79            object,
80            usage,
81        }: response::streaming::FunctionExecutionChunk,
82    ) -> Self {
83        Self {
84            id,
85            tasks: tasks.into_iter().map(super::Task::from).collect(),
86            tasks_errors: tasks_errors.unwrap_or(false),
87            reasoning: reasoning.map(super::ReasoningSummary::from),
88            output: output.unwrap_or(response::Output {
89                output: functions::expression::TaskOutputOwned::Err {
90                    error: serde_json::Value::Null,
91                },
92            }),
93            error,
94            created,
95            function,
96            profile,
97            object: object.into(),
98            usage: usage.unwrap_or_default(),
99        }
100    }
101}