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        self.usage.normalize_for_tests();
48        for task in &mut self.tasks {
49            match task {
50                super::Task::VectorCompletion(vt) => {
51                    // `index` reflects arrival order from concurrent
52                    // sub-task streams, which is non-deterministic;
53                    // pin it to the local sibling position (the last
54                    // element of `task_path`) for snapshot stability.
55                    vt.index = vt.task_path.last().copied().unwrap_or(0);
56                    vt.inner.normalize_for_tests();
57                }
58                super::Task::FunctionExecution(ft) => {
59                    ft.index = ft.task_path.last().copied().unwrap_or(0);
60                    ft.inner.normalize_for_tests();
61                }
62            }
63        }
64        self.tasks.sort_by_cached_key(|t| t.snapshot_sort_key());
65    }
66}
67
68impl From<response::streaming::FunctionExecutionChunk> for FunctionExecution {
69    fn from(
70        response::streaming::FunctionExecutionChunk {
71            id,
72            tasks,
73            tasks_errors,
74            reasoning,
75            output,
76            error,
77            created,
78            function,
79            profile,
80            object,
81            usage,
82        }: response::streaming::FunctionExecutionChunk,
83    ) -> Self {
84        Self {
85            id,
86            tasks: tasks.into_iter().map(super::Task::from).collect(),
87            tasks_errors: tasks_errors.unwrap_or(false),
88            reasoning: reasoning.map(super::ReasoningSummary::from),
89            output: output.unwrap_or(response::Output {
90                output: functions::expression::TaskOutputOwned::Err {
91                    error: serde_json::Value::Null,
92                },
93            }),
94            error,
95            created,
96            function,
97            profile,
98            object: object.into(),
99            usage: usage.unwrap_or_default(),
100        }
101    }
102}