mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-17 08:01:03 +00:00
feat: show mock agent hint bubble in empty state
This commit is contained in:
parent
02c9201bda
commit
50b5289e47
11 changed files with 186 additions and 2 deletions
|
|
@ -22,6 +22,7 @@ Capabilities tell you which features are supported for the selected agent:
|
||||||
- `questions` and `permissions` indicate HITL flows.
|
- `questions` and `permissions` indicate HITL flows.
|
||||||
- `plan_mode` indicates that the agent supports plan-only execution.
|
- `plan_mode` indicates that the agent supports plan-only execution.
|
||||||
- `reasoning` and `status` indicate that the agent can emit reasoning/status content parts.
|
- `reasoning` and `status` indicate that the agent can emit reasoning/status content parts.
|
||||||
|
- `item_started` indicates that the agent emits `item.started` on its own; when false the daemon will emit a synthetic `item.started` immediately after sending a user message.
|
||||||
|
|
||||||
Use these to enable or disable UI affordances (tool panels, approval buttons, etc.).
|
Use these to enable or disable UI affordances (tool panels, approval buttons, etc.).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -668,6 +668,7 @@
|
||||||
"fileChanges",
|
"fileChanges",
|
||||||
"mcpTools",
|
"mcpTools",
|
||||||
"streamingDeltas",
|
"streamingDeltas",
|
||||||
|
"itemStarted",
|
||||||
"sharedProcess"
|
"sharedProcess"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -686,6 +687,9 @@
|
||||||
"images": {
|
"images": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"itemStarted": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"mcpTools": {
|
"mcpTools": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -723,6 +723,26 @@
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mock-agent-hint {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--border-2);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
max-width: 320px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mock-agent-hint code {
|
||||||
|
background: var(--border-2);
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Consolas, monospace;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
.empty-state-menu-wrapper {
|
.empty-state-menu-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -853,6 +853,7 @@ export default function App() {
|
||||||
agentsLoading={agentsLoading}
|
agentsLoading={agentsLoading}
|
||||||
agentsError={agentsError}
|
agentsError={agentsError}
|
||||||
messagesEndRef={messagesEndRef}
|
messagesEndRef={messagesEndRef}
|
||||||
|
agentId={agentId}
|
||||||
agentLabel={agentLabel}
|
agentLabel={agentLabel}
|
||||||
agentMode={agentMode}
|
agentMode={agentMode}
|
||||||
permissionMode={permissionMode}
|
permissionMode={permissionMode}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import {
|
||||||
Activity,
|
Activity,
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
Brain,
|
Brain,
|
||||||
|
CircleDot,
|
||||||
Download,
|
Download,
|
||||||
FileDiff,
|
FileDiff,
|
||||||
Gauge,
|
Gauge,
|
||||||
|
|
@ -35,7 +36,8 @@ const badges = [
|
||||||
{ key: "commandExecution", label: "Commands", icon: Terminal },
|
{ key: "commandExecution", label: "Commands", icon: Terminal },
|
||||||
{ key: "fileChanges", label: "File Changes", icon: FileDiff },
|
{ key: "fileChanges", label: "File Changes", icon: FileDiff },
|
||||||
{ key: "mcpTools", label: "MCP", icon: Plug },
|
{ key: "mcpTools", label: "MCP", icon: Plug },
|
||||||
{ key: "streamingDeltas", label: "Deltas", icon: Activity }
|
{ key: "streamingDeltas", label: "Deltas", icon: Activity },
|
||||||
|
{ key: "itemStarted", label: "Item Start", icon: CircleDot }
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
type BadgeItem = (typeof badges)[number];
|
type BadgeItem = (typeof badges)[number];
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ const ChatPanel = ({
|
||||||
agentsLoading,
|
agentsLoading,
|
||||||
agentsError,
|
agentsError,
|
||||||
messagesEndRef,
|
messagesEndRef,
|
||||||
|
agentId,
|
||||||
agentLabel,
|
agentLabel,
|
||||||
agentMode,
|
agentMode,
|
||||||
permissionMode,
|
permissionMode,
|
||||||
|
|
@ -63,6 +64,7 @@ const ChatPanel = ({
|
||||||
agentsLoading: boolean;
|
agentsLoading: boolean;
|
||||||
agentsError: string | null;
|
agentsError: string | null;
|
||||||
messagesEndRef: React.RefObject<HTMLDivElement>;
|
messagesEndRef: React.RefObject<HTMLDivElement>;
|
||||||
|
agentId: string;
|
||||||
agentLabel: string;
|
agentLabel: string;
|
||||||
agentMode: string;
|
agentMode: string;
|
||||||
permissionMode: string;
|
permissionMode: string;
|
||||||
|
|
@ -230,6 +232,11 @@ const ChatPanel = ({
|
||||||
<Terminal className="empty-state-icon" />
|
<Terminal className="empty-state-icon" />
|
||||||
<div className="empty-state-title">Ready to Chat</div>
|
<div className="empty-state-title">Ready to Chat</div>
|
||||||
<p className="empty-state-text">Send a message to start a conversation with the agent.</p>
|
<p className="empty-state-text">Send a message to start a conversation with the agent.</p>
|
||||||
|
{agentId === "mock" && (
|
||||||
|
<div className="mock-agent-hint">
|
||||||
|
The mock agent simulates agent responses for testing the inspector UI without requiring API credentials. Send <code>help</code> for available commands.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<ChatMessages
|
<ChatMessages
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ export type AgentCapabilitiesView = AgentCapabilities & {
|
||||||
fileChanges?: boolean;
|
fileChanges?: boolean;
|
||||||
mcpTools?: boolean;
|
mcpTools?: boolean;
|
||||||
streamingDeltas?: boolean;
|
streamingDeltas?: boolean;
|
||||||
|
itemStarted?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const emptyCapabilities: AgentCapabilitiesView = {
|
export const emptyCapabilities: AgentCapabilitiesView = {
|
||||||
|
|
@ -32,5 +33,6 @@ export const emptyCapabilities: AgentCapabilitiesView = {
|
||||||
fileChanges: false,
|
fileChanges: false,
|
||||||
mcpTools: false,
|
mcpTools: false,
|
||||||
streamingDeltas: false,
|
streamingDeltas: false,
|
||||||
|
itemStarted: false,
|
||||||
sharedProcess: false
|
sharedProcess: false
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ export interface components {
|
||||||
fileAttachments: boolean;
|
fileAttachments: boolean;
|
||||||
fileChanges: boolean;
|
fileChanges: boolean;
|
||||||
images: boolean;
|
images: boolean;
|
||||||
|
itemStarted: boolean;
|
||||||
mcpTools: boolean;
|
mcpTools: boolean;
|
||||||
permissions: boolean;
|
permissions: boolean;
|
||||||
planMode: boolean;
|
planMode: boolean;
|
||||||
|
|
|
||||||
|
|
@ -105,3 +105,10 @@ cargo test -p sandbox-agent --test http_endpoints
|
||||||
## Universal Schema
|
## Universal Schema
|
||||||
|
|
||||||
When modifying agent conversion code in `server/packages/universal-agent-schema/src/agents/` or adding/changing properties on the universal schema, update the feature matrix in `README.md` to reflect which agents support which features.
|
When modifying agent conversion code in `server/packages/universal-agent-schema/src/agents/` or adding/changing properties on the universal schema, update the feature matrix in `README.md` to reflect which agents support which features.
|
||||||
|
|
||||||
|
## Capabilities sync
|
||||||
|
|
||||||
|
When updating agent capabilities (flags or values), keep them in sync across:
|
||||||
|
- `README.md` (feature matrix / documented support)
|
||||||
|
- server Rust implementation (`AgentCapabilities` + `agent_capabilities_for`)
|
||||||
|
- frontend capability views/badges (Inspector UI)
|
||||||
|
|
|
||||||
|
|
@ -267,6 +267,8 @@ struct SessionState {
|
||||||
session_started_emitted: bool,
|
session_started_emitted: bool,
|
||||||
last_claude_message_id: Option<String>,
|
last_claude_message_id: Option<String>,
|
||||||
claude_message_counter: u64,
|
claude_message_counter: u64,
|
||||||
|
pending_assistant_native_ids: VecDeque<String>,
|
||||||
|
pending_assistant_counter: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -322,9 +324,40 @@ impl SessionState {
|
||||||
session_started_emitted: false,
|
session_started_emitted: false,
|
||||||
last_claude_message_id: None,
|
last_claude_message_id: None,
|
||||||
claude_message_counter: 0,
|
claude_message_counter: 0,
|
||||||
|
pending_assistant_native_ids: VecDeque::new(),
|
||||||
|
pending_assistant_counter: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn next_pending_assistant_native_id(&mut self) -> String {
|
||||||
|
self.pending_assistant_counter += 1;
|
||||||
|
format!(
|
||||||
|
"{}_pending_assistant_{}",
|
||||||
|
self.session_id, self.pending_assistant_counter
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enqueue_pending_assistant_start(&mut self) -> EventConversion {
|
||||||
|
let native_item_id = self.next_pending_assistant_native_id();
|
||||||
|
self.pending_assistant_native_ids
|
||||||
|
.push_back(native_item_id.clone());
|
||||||
|
EventConversion::new(
|
||||||
|
UniversalEventType::ItemStarted,
|
||||||
|
UniversalEventData::Item(ItemEventData {
|
||||||
|
item: UniversalItem {
|
||||||
|
item_id: String::new(),
|
||||||
|
native_item_id: Some(native_item_id),
|
||||||
|
parent_id: None,
|
||||||
|
kind: ItemKind::Message,
|
||||||
|
role: Some(ItemRole::Assistant),
|
||||||
|
content: Vec::new(),
|
||||||
|
status: ItemStatus::InProgress,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.synthetic()
|
||||||
|
}
|
||||||
|
|
||||||
fn record_conversions(&mut self, conversions: Vec<EventConversion>) -> Vec<UniversalEvent> {
|
fn record_conversions(&mut self, conversions: Vec<EventConversion>) -> Vec<UniversalEvent> {
|
||||||
let mut events = Vec::new();
|
let mut events = Vec::new();
|
||||||
for conversion in conversions {
|
for conversion in conversions {
|
||||||
|
|
@ -357,6 +390,54 @@ impl SessionState {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut conversions = Vec::new();
|
let mut conversions = Vec::new();
|
||||||
|
if !agent_supports_item_started(self.agent) {
|
||||||
|
if conversion.event_type == UniversalEventType::ItemStarted {
|
||||||
|
if let UniversalEventData::Item(ref data) = conversion.data {
|
||||||
|
let is_assistant_message = data.item.kind == ItemKind::Message
|
||||||
|
&& matches!(data.item.role, Some(ItemRole::Assistant));
|
||||||
|
if is_assistant_message {
|
||||||
|
let keep = data
|
||||||
|
.item
|
||||||
|
.native_item_id
|
||||||
|
.as_ref()
|
||||||
|
.map(|id| self.pending_assistant_native_ids.contains(id))
|
||||||
|
.unwrap_or(false);
|
||||||
|
if !keep {
|
||||||
|
return conversions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match conversion.event_type {
|
||||||
|
UniversalEventType::ItemCompleted => {
|
||||||
|
if let UniversalEventData::Item(ref mut data) = conversion.data {
|
||||||
|
let is_assistant_message = data.item.kind == ItemKind::Message
|
||||||
|
&& matches!(data.item.role, Some(ItemRole::Assistant));
|
||||||
|
if is_assistant_message {
|
||||||
|
if let Some(pending) = self.pending_assistant_native_ids.pop_front() {
|
||||||
|
data.item.native_item_id = Some(pending);
|
||||||
|
data.item.item_id.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UniversalEventType::ItemDelta => {
|
||||||
|
if let UniversalEventData::ItemDelta(ref mut data) = conversion.data {
|
||||||
|
let is_user = data
|
||||||
|
.native_item_id
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|id| id.starts_with("user_"));
|
||||||
|
if !is_user {
|
||||||
|
if let Some(pending) = self.pending_assistant_native_ids.front() {
|
||||||
|
data.native_item_id = Some(pending.clone());
|
||||||
|
data.item_id.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
match conversion.event_type {
|
match conversion.event_type {
|
||||||
UniversalEventType::ItemStarted | UniversalEventType::ItemCompleted => {
|
UniversalEventType::ItemStarted | UniversalEventType::ItemCompleted => {
|
||||||
if let UniversalEventData::Item(ref mut data) = conversion.data {
|
if let UniversalEventData::Item(ref mut data) = conversion.data {
|
||||||
|
|
@ -1505,11 +1586,21 @@ impl SessionManager {
|
||||||
self.ensure_opencode_stream(session_id.clone()).await?;
|
self.ensure_opencode_stream(session_id.clone()).await?;
|
||||||
self.send_opencode_prompt(&session_snapshot, &message)
|
self.send_opencode_prompt(&session_snapshot, &message)
|
||||||
.await?;
|
.await?;
|
||||||
|
if !agent_supports_item_started(session_snapshot.agent) {
|
||||||
|
let _ = self
|
||||||
|
.emit_synthetic_assistant_start(&session_snapshot.session_id)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
if session_snapshot.agent == AgentId::Codex {
|
if session_snapshot.agent == AgentId::Codex {
|
||||||
// Use the shared Codex app-server
|
// Use the shared Codex app-server
|
||||||
self.send_codex_turn(&session_snapshot, &message).await?;
|
self.send_codex_turn(&session_snapshot, &message).await?;
|
||||||
|
if !agent_supports_item_started(session_snapshot.agent) {
|
||||||
|
let _ = self
|
||||||
|
.emit_synthetic_assistant_start(&session_snapshot.session_id)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1537,6 +1628,12 @@ impl SessionManager {
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let spawn_result = spawn_result.map_err(|err| map_spawn_error(agent_id, err))?;
|
let spawn_result = spawn_result.map_err(|err| map_spawn_error(agent_id, err))?;
|
||||||
|
if !agent_supports_item_started(session_snapshot.agent) {
|
||||||
|
let _ = self
|
||||||
|
.emit_synthetic_assistant_start(&session_snapshot.session_id)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
let manager = Arc::clone(self);
|
let manager = Arc::clone(self);
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
manager
|
manager
|
||||||
|
|
@ -1547,6 +1644,23 @@ impl SessionManager {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn emit_synthetic_assistant_start(
|
||||||
|
&self,
|
||||||
|
session_id: &str,
|
||||||
|
) -> Result<(), SandboxError> {
|
||||||
|
let conversion = {
|
||||||
|
let mut sessions = self.sessions.lock().await;
|
||||||
|
let session = Self::session_mut(&mut sessions, session_id).ok_or_else(|| {
|
||||||
|
SandboxError::SessionNotFound {
|
||||||
|
session_id: session_id.to_string(),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
session.enqueue_pending_assistant_start()
|
||||||
|
};
|
||||||
|
let _ = self.record_conversions(session_id, vec![conversion]).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Reopens a session that was ended by an agent process completing.
|
/// Reopens a session that was ended by an agent process completing.
|
||||||
/// This allows resumable agents (Claude, Amp, OpenCode) to continue conversations.
|
/// This allows resumable agents (Claude, Amp, OpenCode) to continue conversations.
|
||||||
async fn reopen_session_if_ended(&self, session_id: &str) {
|
async fn reopen_session_if_ended(&self, session_id: &str) {
|
||||||
|
|
@ -3023,6 +3137,7 @@ pub struct AgentCapabilities {
|
||||||
pub file_changes: bool,
|
pub file_changes: bool,
|
||||||
pub mcp_tools: bool,
|
pub mcp_tools: bool,
|
||||||
pub streaming_deltas: bool,
|
pub streaming_deltas: bool,
|
||||||
|
pub item_started: bool,
|
||||||
/// Whether this agent uses a shared long-running server process (vs per-turn subprocess)
|
/// Whether this agent uses a shared long-running server process (vs per-turn subprocess)
|
||||||
pub shared_process: bool,
|
pub shared_process: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -3602,6 +3717,10 @@ fn agent_supports_resume(agent: AgentId) -> bool {
|
||||||
matches!(agent, AgentId::Claude | AgentId::Amp | AgentId::Opencode | AgentId::Codex)
|
matches!(agent, AgentId::Claude | AgentId::Amp | AgentId::Opencode | AgentId::Codex)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn agent_supports_item_started(agent: AgentId) -> bool {
|
||||||
|
agent_capabilities_for(agent).item_started
|
||||||
|
}
|
||||||
|
|
||||||
fn agent_capabilities_for(agent: AgentId) -> AgentCapabilities {
|
fn agent_capabilities_for(agent: AgentId) -> AgentCapabilities {
|
||||||
match agent {
|
match agent {
|
||||||
// Headless Claude CLI does not expose AskUserQuestion and does not emit tool_result,
|
// Headless Claude CLI does not expose AskUserQuestion and does not emit tool_result,
|
||||||
|
|
@ -3623,6 +3742,7 @@ fn agent_capabilities_for(agent: AgentId) -> AgentCapabilities {
|
||||||
file_changes: false,
|
file_changes: false,
|
||||||
mcp_tools: false,
|
mcp_tools: false,
|
||||||
streaming_deltas: false,
|
streaming_deltas: false,
|
||||||
|
item_started: false,
|
||||||
shared_process: false, // per-turn subprocess with --resume
|
shared_process: false, // per-turn subprocess with --resume
|
||||||
},
|
},
|
||||||
AgentId::Codex => AgentCapabilities {
|
AgentId::Codex => AgentCapabilities {
|
||||||
|
|
@ -3642,6 +3762,7 @@ fn agent_capabilities_for(agent: AgentId) -> AgentCapabilities {
|
||||||
file_changes: true,
|
file_changes: true,
|
||||||
mcp_tools: true,
|
mcp_tools: true,
|
||||||
streaming_deltas: true,
|
streaming_deltas: true,
|
||||||
|
item_started: true,
|
||||||
shared_process: true, // shared app-server via JSON-RPC
|
shared_process: true, // shared app-server via JSON-RPC
|
||||||
},
|
},
|
||||||
AgentId::Opencode => AgentCapabilities {
|
AgentId::Opencode => AgentCapabilities {
|
||||||
|
|
@ -3661,6 +3782,7 @@ fn agent_capabilities_for(agent: AgentId) -> AgentCapabilities {
|
||||||
file_changes: false,
|
file_changes: false,
|
||||||
mcp_tools: false,
|
mcp_tools: false,
|
||||||
streaming_deltas: true,
|
streaming_deltas: true,
|
||||||
|
item_started: true,
|
||||||
shared_process: true, // shared HTTP server
|
shared_process: true, // shared HTTP server
|
||||||
},
|
},
|
||||||
AgentId::Amp => AgentCapabilities {
|
AgentId::Amp => AgentCapabilities {
|
||||||
|
|
@ -3680,6 +3802,7 @@ fn agent_capabilities_for(agent: AgentId) -> AgentCapabilities {
|
||||||
file_changes: false,
|
file_changes: false,
|
||||||
mcp_tools: false,
|
mcp_tools: false,
|
||||||
streaming_deltas: false,
|
streaming_deltas: false,
|
||||||
|
item_started: false,
|
||||||
shared_process: false, // per-turn subprocess with --continue
|
shared_process: false, // per-turn subprocess with --continue
|
||||||
},
|
},
|
||||||
AgentId::Mock => AgentCapabilities {
|
AgentId::Mock => AgentCapabilities {
|
||||||
|
|
@ -3699,6 +3822,7 @@ fn agent_capabilities_for(agent: AgentId) -> AgentCapabilities {
|
||||||
file_changes: true,
|
file_changes: true,
|
||||||
mcp_tools: true,
|
mcp_tools: true,
|
||||||
streaming_deltas: true,
|
streaming_deltas: true,
|
||||||
|
item_started: true,
|
||||||
shared_process: false, // in-memory mock (no subprocess)
|
shared_process: false, // in-memory mock (no subprocess)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -572,12 +572,27 @@ fn normalize_item(item: &Value) -> Value {
|
||||||
map.insert("status".to_string(), Value::String(status.to_string()));
|
map.insert("status".to_string(), Value::String(status.to_string()));
|
||||||
}
|
}
|
||||||
if let Some(content) = item.get("content").and_then(Value::as_array) {
|
if let Some(content) = item.get("content").and_then(Value::as_array) {
|
||||||
let types = content
|
let mut types = content
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|part| part.get("type").and_then(Value::as_str))
|
.filter_map(|part| part.get("type").and_then(Value::as_str))
|
||||||
.filter(|value| *value != "reasoning" && *value != "status")
|
.filter(|value| *value != "reasoning" && *value != "status")
|
||||||
.map(|value| Value::String(value.to_string()))
|
.map(|value| Value::String(value.to_string()))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
let is_assistant_message = item
|
||||||
|
.get("kind")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.is_some_and(|kind| kind == "message")
|
||||||
|
&& item
|
||||||
|
.get("role")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.is_some_and(|role| role == "assistant");
|
||||||
|
let is_in_progress = item
|
||||||
|
.get("status")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.is_some_and(|status| status == "in_progress");
|
||||||
|
if types.is_empty() && is_assistant_message && is_in_progress {
|
||||||
|
types.push(Value::String("text".to_string()));
|
||||||
|
}
|
||||||
map.insert("content_types".to_string(), Value::Array(types));
|
map.insert("content_types".to_string(), Value::Array(types));
|
||||||
}
|
}
|
||||||
Value::Object(map)
|
Value::Object(map)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue