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 serde::{Deserialize, Serialize};
8use schemars::JsonSchema;
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    /// Token for retrying this execution with cached votes.
27    pub retry_token: Option<String>,
28    /// Unix timestamp when the execution was created.
29    pub created: u64,
30    /// The function used (if remote).
31    pub function: Option<crate::RemotePath>,
32    /// The profile used (if remote).
33    pub profile: Option<crate::RemotePath>,
34    /// Object type identifier.
35    pub object: super::Object,
36    /// Aggregated token and cost usage.
37    pub usage: agent::completions::response::Usage,
38}
39
40impl FunctionExecution {
41    pub fn any_usage(&self) -> bool {
42        self.usage.any_usage()
43    }
44
45    /// Normalize non-deterministic fields for test snapshot comparison.
46    pub fn normalize_for_tests(&mut self) {
47        self.id = String::new();
48        self.created = 0;
49        self.retry_token = None;
50        for task in &mut self.tasks {
51            match task {
52                super::Task::VectorCompletion(vt) => {
53                    // `index` reflects arrival order from concurrent
54                    // sub-task streams, which is non-deterministic;
55                    // pin it to the local sibling position (the last
56                    // element of `task_path`) for snapshot stability.
57                    vt.index = vt.task_path.last().copied().unwrap_or(0);
58                    vt.inner.normalize_for_tests();
59                }
60                super::Task::FunctionExecution(ft) => {
61                    ft.index = ft.task_path.last().copied().unwrap_or(0);
62                    ft.inner.normalize_for_tests();
63                }
64            }
65        }
66        self.tasks.sort_by_cached_key(|t| t.snapshot_sort_key());
67    }
68}
69
70impl From<response::streaming::FunctionExecutionChunk> for FunctionExecution {
71    fn from(
72        response::streaming::FunctionExecutionChunk {
73            id,
74            tasks,
75            tasks_errors,
76            reasoning,
77            output,
78            error,
79            retry_token,
80            created,
81            function,
82            profile,
83            object,
84            usage,
85        }: response::streaming::FunctionExecutionChunk,
86    ) -> Self {
87        Self {
88            id,
89            tasks: tasks.into_iter().map(super::Task::from).collect(),
90            tasks_errors: tasks_errors.unwrap_or(false),
91            reasoning: reasoning.map(super::ReasoningSummary::from),
92            output: output.unwrap_or(response::Output {
93                output: functions::expression::TaskOutputOwned::Err {
94                    error: serde_json::Value::Null,
95                },
96            }),
97            error,
98            retry_token,
99            created,
100            function,
101            profile,
102            object: object.into(),
103            usage: usage.unwrap_or_default(),
104        }
105    }
106}