Skip to main content

objectiveai_sdk/cli/command/agents/logs/open/
mod.rs

1//! `agents logs read id` — fetch one logged row by its
2//! `logs.messages."index"` BIGSERIAL. The handler resolves the row's
3//! target table via the `logs.message_table` discriminator and
4//! returns a typed [`Response`] variant carrying that table's
5//! payload — no `serde_json::Value` anywhere in the public shape.
6//!
7//! The underlying `logs.*` tables collapse into 8 variants:
8//!
9//! 1. **Request-blob tiers (3)** — agent / vector / function
10//!    request bodies. The JSONB column round-trips through the
11//!    matching SDK `…CreateParams` type so callers see a proper
12//!    typed object, not a raw blob.
13//! 2. **Content payloads (5)** — `Text` / `Image` / `Audio` /
14//!    `Video` / `File`. The `Text` variant subsumes all text-bearing
15//!    rows (refusal, reasoning, assistant content text, tool content
16//!    text, AND a tool call's `arguments`); media variants carry the
17//!    SDK media type directly so MCP rendering routes through the
18//!    existing `ContentBlock` projections.
19//!
20//! An `assistant_response_tool_calls` row reads back as `Text` (its
21//! `arguments`) — the call's metadata (function_name / tool_call_id /
22//! tool_call_index) is surfaced inline by `agents logs read all`. The
23//! `tool_response` container head is no longer registered in
24//! `logs.messages`, so it is never addressable here.
25
26use crate::agent::completions::message::{File, ImageUrl, InputAudio, VideoUrl};
27use crate::agent::completions::request::AgentCompletionCreateParams;
28use crate::cli::command::CommandRequest;
29use crate::functions::executions::request::FunctionExecutionCreateParams;
30use crate::vector::completions::request::VectorCompletionCreateParams;
31
32#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
33#[schemars(rename = "cli.command.agents.logs.open.Request")]
34pub struct Request {
35    pub path_type: Path,
36    pub id: i64,
37    #[serde(flatten)]
38    pub base: crate::cli::command::RequestBase,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
42#[schemars(rename = "cli.command.agents.logs.open.Path")]
43pub enum Path {
44    #[serde(rename = "agents/logs/open")]
45    AgentsLogsOpen,
46}
47
48impl CommandRequest for Request {
49    fn request_base(&self) -> &crate::cli::command::RequestBase {
50        &self.base
51    }
52
53    fn request_base_mut(&mut self) -> Option<&mut crate::cli::command::RequestBase> {
54        Some(&mut self.base)
55    }
56}
57
58/// Resolved payload for one `logs.messages."index"`. Tagged by
59/// `type`, snake_case discriminant. The MCP projection in
60/// [`CommandResponse::into_mcp`] hands media variants over as
61/// [`ContentBlock`]s and text as a bare JSON string — matching the
62/// existing `agents queue read id` projection of `RichContentPart`.
63/// The three request-blob variants render as JSONL with their full
64/// typed `…CreateParams` body so callers can introspect request
65/// metadata.
66///
67/// [`ContentBlock`]: crate::mcp::tool::ContentBlock
68#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
69#[serde(tag = "type", rename_all = "snake_case")]
70#[schemars(rename = "cli.command.agents.logs.open.Response")]
71pub enum Response {
72    #[schemars(title = "AgentCompletionRequest")]
73    AgentCompletionRequest {
74        response_id: String,
75        sender_agent_instance_hierarchy: String,
76        body: AgentCompletionCreateParams,
77        created_at: i64,
78    },
79    #[schemars(title = "VectorCompletionRequest")]
80    VectorCompletionRequest {
81        response_id: String,
82        sender_agent_instance_hierarchy: String,
83        body: VectorCompletionCreateParams,
84        created_at: i64,
85    },
86    #[schemars(title = "FunctionExecutionRequest")]
87    FunctionExecutionRequest {
88        response_id: String,
89        sender_agent_instance_hierarchy: String,
90        body: FunctionExecutionCreateParams,
91        created_at: i64,
92    },
93    #[schemars(title = "Text")]
94    Text { text: String },
95    #[schemars(title = "Image")]
96    Image(ImageUrl),
97    #[schemars(title = "Audio")]
98    Audio(InputAudio),
99    #[schemars(title = "Video")]
100    Video(VideoUrl),
101    #[schemars(title = "File")]
102    File(File),
103    /// A logged failure (`objectiveai.errors`) — the CLI's user-facing
104    /// error value: a structured object for API response errors, a
105    /// plain string otherwise.
106    #[schemars(title = "Error")]
107    Error { error: serde_json::Value },
108}
109
110#[derive(clap::Args)]
111#[command(group(clap::ArgGroup::new("id_required").required(true).args(["id"])))]
112pub struct Args {
113    /// `logs.messages."index"` — the BIGSERIAL position of the
114    /// event in the cross-agent history.
115    #[arg(long)]
116    pub id: Option<i64>,
117    #[command(flatten)]
118    pub base: crate::cli::command::RequestBaseArgs,
119}
120
121#[derive(clap::Args)]
122#[command(args_conflicts_with_subcommands = true)]
123pub struct Command {
124    #[command(flatten)]
125    pub args: Args,
126    #[command(subcommand)]
127    pub schema: Option<Schema>,
128}
129
130#[derive(clap::Subcommand)]
131pub enum Schema {
132    /// Emit the JSON Schema for this leaf's `Request` type and exit.
133    RequestSchema(request_schema::Args),
134    /// Emit the JSON Schema for this leaf's `Response` type and exit.
135    ResponseSchema(response_schema::Args),
136}
137
138impl TryFrom<Args> for Request {
139    type Error = crate::cli::command::FromArgsError;
140    fn try_from(args: Args) -> Result<Self, Self::Error> {
141        Ok(Self {
142            path_type: Path::AgentsLogsOpen,
143            id: args.id.ok_or_else(|| {
144                crate::cli::command::FromArgsError::path_parse(
145                    "id",
146                    "--id is required".to_string(),
147                )
148            })?,
149            base: args.base.into(),
150        })
151    }
152}
153
154#[cfg(feature = "mcp")]
155impl crate::cli::command::CommandResponse for Response {
156    fn into_mcp(self) -> crate::cli::command::McpResponseItem {
157        use crate::cli::command::CommandResponse;
158        match self {
159            // Content payloads delegate to the existing inner-type
160            // projections so they ride MCP exactly the way bare
161            // `RichContentPart` does today.
162            Response::Text { text } => text.into_mcp(),
163            Response::Image(image_url) => image_url.into_mcp(),
164            Response::Audio(input_audio) => input_audio.into_mcp(),
165            Response::Video(video_url) => video_url.into_mcp(),
166            Response::File(file) => file.into_mcp(),
167            // Everything else: the full typed variant rides as JSONL.
168            other => crate::cli::command::McpResponseItem::JSONL(
169                serde_json::to_value(other).unwrap(),
170            ),
171        }
172    }
173}
174
175#[cfg(feature = "cli-executor")]
176pub async fn execute<E: crate::cli::command::CommandExecutor>(
177    executor: &E,
178    mut request: Request,
179
180        agent_arguments: Option<&crate::cli::command::AgentArguments>,
181    ) -> Result<Response, E::Error> {
182    request.base.clear_transform();
183    executor.execute_one(request, agent_arguments).await
184}
185
186#[cfg(feature = "cli-executor")]
187pub async fn execute_transform<E: crate::cli::command::CommandExecutor>(
188    executor: &E,
189    mut request: Request,
190    transform: crate::cli::command::Transform,
191
192        agent_arguments: Option<&crate::cli::command::AgentArguments>,
193    ) -> Result<serde_json::Value, E::Error> {
194    request.base.set_transform(transform);
195    executor.execute_one(request, agent_arguments).await
196}
197
198pub mod request_schema;
199
200pub mod response_schema;
201
202/// One `/listen` broadcast run of `agents logs open`: the actual
203/// [`Request`], the producer's
204/// [`AgentArguments`](crate::cli::command::AgentArguments), and the
205/// unary response future. See [`crate::cli::websocket_listener`].
206#[cfg(feature = "cli-listener")]
207pub struct ListenerExecution {
208    pub request: Request,
209    pub agent_arguments: crate::cli::command::AgentArguments,
210    pub response: crate::cli::websocket_listener::UnaryResponse<Response>,
211}