Skip to main content

objectiveai_sdk/agent/mock/
call.rs

1//! Deterministic-script override types for the mock agent.
2//!
3//! When [`super::AgentBase::calls`] is `Some(vec![Call{...}, ...])`,
4//! the mock agent skips its usual per-turn RNG response selection and
5//! instead emits each [`Call`] as its own assistant turn — every
6//! [`CallToolCall`] in `tool_calls` first (as tool-call deltas), then
7//! `content` (as content deltas) — in array order. Each subsequent
8//! turn walks the continuation to count how many [`Call`]s have
9//! already been satisfied; the next un-matched [`Call`] is what that
10//! turn emits. Once every [`Call`] has been satisfied, the mock falls
11//! through to its normal mode-driven dispatcher.
12
13use schemars::JsonSchema;
14use serde::{Deserialize, Serialize};
15
16/// One scripted assistant turn for the mock agent's deterministic
17/// `calls` override (see [`super::AgentBase::calls`]).
18///
19/// When the mock fires this `Call`, it emits every entry in
20/// `tool_calls` first (as tool-call deltas), then `content` (as
21/// content deltas), finishing the turn after the last content chunk.
22/// The match check on the continuation compares
23/// `(tool_calls[i].name, tool_calls[i].arguments)` pairs plus the
24/// full `content` string — call ids are intentionally excluded since
25/// they're random per-emit.
26#[derive(
27    Clone,
28    Debug,
29    Default,
30    PartialEq,
31    Serialize,
32    Deserialize,
33    JsonSchema,
34    arbitrary::Arbitrary,
35)]
36#[schemars(rename = "agent.mock.Call")]
37pub struct Call {
38    /// Tool calls emitted by this turn. Empty `Vec` is allowed and
39    /// means "no tool calls, just content."
40    #[serde(default, skip_serializing_if = "Vec::is_empty")]
41    #[schemars(extend("omitempty" = true))]
42    pub tool_calls: Vec<CallToolCall>,
43
44    /// Assistant text content emitted *after* the tool calls. Plain
45    /// `String` — the mock's chunk-emission path only produces
46    /// `RichContent::Text`, so multimodal content was never possible
47    /// here anyway. An empty string is treated as "no content" by
48    /// the emitter (the wire shape becomes `content: None`) and
49    /// matches an assistant message with `content: None`.
50    pub content: String,
51}
52
53/// A single tool call within a [`Call`]. Identifies the tool by
54/// `name` and carries the exact JSON-encoded arguments to pass. No
55/// `id` field — call ids on the wire are minted randomly per emit
56/// and are ignored by the override's match check.
57#[derive(
58    Clone,
59    Debug,
60    Default,
61    PartialEq,
62    Serialize,
63    Deserialize,
64    JsonSchema,
65    arbitrary::Arbitrary,
66)]
67#[schemars(rename = "agent.mock.CallToolCall")]
68pub struct CallToolCall {
69    /// Tool name. Matched against the upstream tool surface at call
70    /// time the same way a model-emitted tool call would be.
71    pub name: String,
72    /// JSON-string arguments — same wire shape as
73    /// `AssistantToolCallFunction::arguments`. Passed through to the
74    /// emitted tool call verbatim; validation is the downstream
75    /// `tools/call` handler's job.
76    pub arguments: String,
77}