Compare commits
1 Commits
rcc/cost
...
rcc/thinki
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c14196c730 |
@@ -912,6 +912,7 @@ mod tests {
|
|||||||
system: None,
|
system: None,
|
||||||
tools: None,
|
tools: None,
|
||||||
tool_choice: None,
|
tool_choice: None,
|
||||||
|
thinking: None,
|
||||||
stream: false,
|
stream: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -13,5 +13,5 @@ pub use types::{
|
|||||||
ContentBlockDelta, ContentBlockDeltaEvent, ContentBlockStartEvent, ContentBlockStopEvent,
|
ContentBlockDelta, ContentBlockDeltaEvent, ContentBlockStartEvent, ContentBlockStopEvent,
|
||||||
InputContentBlock, InputMessage, MessageDelta, MessageDeltaEvent, MessageRequest,
|
InputContentBlock, InputMessage, MessageDelta, MessageDeltaEvent, MessageRequest,
|
||||||
MessageResponse, MessageStartEvent, MessageStopEvent, OutputContentBlock, StreamEvent,
|
MessageResponse, MessageStartEvent, MessageStopEvent, OutputContentBlock, StreamEvent,
|
||||||
ToolChoice, ToolDefinition, ToolResultContentBlock, Usage,
|
ThinkingConfig, ToolChoice, ToolDefinition, ToolResultContentBlock, Usage,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ pub struct MessageRequest {
|
|||||||
pub tools: Option<Vec<ToolDefinition>>,
|
pub tools: Option<Vec<ToolDefinition>>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub tool_choice: Option<ToolChoice>,
|
pub tool_choice: Option<ToolChoice>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub thinking: Option<ThinkingConfig>,
|
||||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||||
pub stream: bool,
|
pub stream: bool,
|
||||||
}
|
}
|
||||||
@@ -24,6 +26,23 @@ impl MessageRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct ThinkingConfig {
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub kind: String,
|
||||||
|
pub budget_tokens: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThinkingConfig {
|
||||||
|
#[must_use]
|
||||||
|
pub fn enabled(budget_tokens: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
kind: "enabled".to_string(),
|
||||||
|
budget_tokens,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct InputMessage {
|
pub struct InputMessage {
|
||||||
pub role: String,
|
pub role: String,
|
||||||
@@ -130,6 +149,11 @@ pub enum OutputContentBlock {
|
|||||||
Text {
|
Text {
|
||||||
text: String,
|
text: String,
|
||||||
},
|
},
|
||||||
|
Thinking {
|
||||||
|
thinking: String,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
signature: Option<String>,
|
||||||
|
},
|
||||||
ToolUse {
|
ToolUse {
|
||||||
id: String,
|
id: String,
|
||||||
name: String,
|
name: String,
|
||||||
@@ -189,6 +213,8 @@ pub struct ContentBlockDeltaEvent {
|
|||||||
#[serde(tag = "type", rename_all = "snake_case")]
|
#[serde(tag = "type", rename_all = "snake_case")]
|
||||||
pub enum ContentBlockDelta {
|
pub enum ContentBlockDelta {
|
||||||
TextDelta { text: String },
|
TextDelta { text: String },
|
||||||
|
ThinkingDelta { thinking: String },
|
||||||
|
SignatureDelta { signature: String },
|
||||||
InputJsonDelta { partial_json: String },
|
InputJsonDelta { partial_json: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -258,6 +258,7 @@ async fn live_stream_smoke_test() {
|
|||||||
system: None,
|
system: None,
|
||||||
tools: None,
|
tools: None,
|
||||||
tool_choice: None,
|
tool_choice: None,
|
||||||
|
thinking: None,
|
||||||
stream: false,
|
stream: false,
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@@ -438,6 +439,7 @@ fn sample_request(stream: bool) -> MessageRequest {
|
|||||||
}),
|
}),
|
||||||
}]),
|
}]),
|
||||||
tool_choice: Some(ToolChoice::Auto),
|
tool_choice: Some(ToolChoice::Auto),
|
||||||
|
thinking: None,
|
||||||
stream,
|
stream,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,12 @@ const SLASH_COMMAND_SPECS: &[SlashCommandSpec] = &[
|
|||||||
argument_hint: None,
|
argument_hint: None,
|
||||||
resume_supported: true,
|
resume_supported: true,
|
||||||
},
|
},
|
||||||
|
SlashCommandSpec {
|
||||||
|
name: "thinking",
|
||||||
|
summary: "Show or toggle extended thinking",
|
||||||
|
argument_hint: Some("[on|off]"),
|
||||||
|
resume_supported: false,
|
||||||
|
},
|
||||||
SlashCommandSpec {
|
SlashCommandSpec {
|
||||||
name: "model",
|
name: "model",
|
||||||
summary: "Show or switch the active model",
|
summary: "Show or switch the active model",
|
||||||
@@ -136,6 +142,9 @@ pub enum SlashCommand {
|
|||||||
Help,
|
Help,
|
||||||
Status,
|
Status,
|
||||||
Compact,
|
Compact,
|
||||||
|
Thinking {
|
||||||
|
enabled: Option<bool>,
|
||||||
|
},
|
||||||
Model {
|
Model {
|
||||||
model: Option<String>,
|
model: Option<String>,
|
||||||
},
|
},
|
||||||
@@ -180,6 +189,13 @@ impl SlashCommand {
|
|||||||
"help" => Self::Help,
|
"help" => Self::Help,
|
||||||
"status" => Self::Status,
|
"status" => Self::Status,
|
||||||
"compact" => Self::Compact,
|
"compact" => Self::Compact,
|
||||||
|
"thinking" => Self::Thinking {
|
||||||
|
enabled: match parts.next() {
|
||||||
|
Some("on") => Some(true),
|
||||||
|
Some("off") => Some(false),
|
||||||
|
Some(_) | None => None,
|
||||||
|
},
|
||||||
|
},
|
||||||
"model" => Self::Model {
|
"model" => Self::Model {
|
||||||
model: parts.next().map(ToOwned::to_owned),
|
model: parts.next().map(ToOwned::to_owned),
|
||||||
},
|
},
|
||||||
@@ -279,6 +295,7 @@ pub fn handle_slash_command(
|
|||||||
session: session.clone(),
|
session: session.clone(),
|
||||||
}),
|
}),
|
||||||
SlashCommand::Status
|
SlashCommand::Status
|
||||||
|
| SlashCommand::Thinking { .. }
|
||||||
| SlashCommand::Model { .. }
|
| SlashCommand::Model { .. }
|
||||||
| SlashCommand::Permissions { .. }
|
| SlashCommand::Permissions { .. }
|
||||||
| SlashCommand::Clear { .. }
|
| SlashCommand::Clear { .. }
|
||||||
@@ -307,6 +324,22 @@ mod tests {
|
|||||||
fn parses_supported_slash_commands() {
|
fn parses_supported_slash_commands() {
|
||||||
assert_eq!(SlashCommand::parse("/help"), Some(SlashCommand::Help));
|
assert_eq!(SlashCommand::parse("/help"), Some(SlashCommand::Help));
|
||||||
assert_eq!(SlashCommand::parse(" /status "), Some(SlashCommand::Status));
|
assert_eq!(SlashCommand::parse(" /status "), Some(SlashCommand::Status));
|
||||||
|
assert_eq!(
|
||||||
|
SlashCommand::parse("/thinking on"),
|
||||||
|
Some(SlashCommand::Thinking {
|
||||||
|
enabled: Some(true),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
SlashCommand::parse("/thinking off"),
|
||||||
|
Some(SlashCommand::Thinking {
|
||||||
|
enabled: Some(false),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
SlashCommand::parse("/thinking"),
|
||||||
|
Some(SlashCommand::Thinking { enabled: None })
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
SlashCommand::parse("/model claude-opus"),
|
SlashCommand::parse("/model claude-opus"),
|
||||||
Some(SlashCommand::Model {
|
Some(SlashCommand::Model {
|
||||||
@@ -374,6 +407,7 @@ mod tests {
|
|||||||
assert!(help.contains("/help"));
|
assert!(help.contains("/help"));
|
||||||
assert!(help.contains("/status"));
|
assert!(help.contains("/status"));
|
||||||
assert!(help.contains("/compact"));
|
assert!(help.contains("/compact"));
|
||||||
|
assert!(help.contains("/thinking [on|off]"));
|
||||||
assert!(help.contains("/model [model]"));
|
assert!(help.contains("/model [model]"));
|
||||||
assert!(help.contains("/permissions [read-only|workspace-write|danger-full-access]"));
|
assert!(help.contains("/permissions [read-only|workspace-write|danger-full-access]"));
|
||||||
assert!(help.contains("/clear [--confirm]"));
|
assert!(help.contains("/clear [--confirm]"));
|
||||||
@@ -386,7 +420,7 @@ mod tests {
|
|||||||
assert!(help.contains("/version"));
|
assert!(help.contains("/version"));
|
||||||
assert!(help.contains("/export [file]"));
|
assert!(help.contains("/export [file]"));
|
||||||
assert!(help.contains("/session [list|switch <session-id>]"));
|
assert!(help.contains("/session [list|switch <session-id>]"));
|
||||||
assert_eq!(slash_command_specs().len(), 15);
|
assert_eq!(slash_command_specs().len(), 16);
|
||||||
assert_eq!(resume_supported_slash_commands().len(), 11);
|
assert_eq!(resume_supported_slash_commands().len(), 11);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -434,6 +468,9 @@ mod tests {
|
|||||||
let session = Session::new();
|
let session = Session::new();
|
||||||
assert!(handle_slash_command("/unknown", &session, CompactionConfig::default()).is_none());
|
assert!(handle_slash_command("/unknown", &session, CompactionConfig::default()).is_none());
|
||||||
assert!(handle_slash_command("/status", &session, CompactionConfig::default()).is_none());
|
assert!(handle_slash_command("/status", &session, CompactionConfig::default()).is_none());
|
||||||
|
assert!(
|
||||||
|
handle_slash_command("/thinking on", &session, CompactionConfig::default()).is_none()
|
||||||
|
);
|
||||||
assert!(
|
assert!(
|
||||||
handle_slash_command("/model claude", &session, CompactionConfig::default()).is_none()
|
handle_slash_command("/model claude", &session, CompactionConfig::default()).is_none()
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ fn summarize_messages(messages: &[ConversationMessage]) -> String {
|
|||||||
.filter_map(|block| match block {
|
.filter_map(|block| match block {
|
||||||
ContentBlock::ToolUse { name, .. } => Some(name.as_str()),
|
ContentBlock::ToolUse { name, .. } => Some(name.as_str()),
|
||||||
ContentBlock::ToolResult { tool_name, .. } => Some(tool_name.as_str()),
|
ContentBlock::ToolResult { tool_name, .. } => Some(tool_name.as_str()),
|
||||||
ContentBlock::Text { .. } => None,
|
ContentBlock::Text { .. } | ContentBlock::Thinking { .. } => None,
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
tool_names.sort_unstable();
|
tool_names.sort_unstable();
|
||||||
@@ -200,6 +200,7 @@ fn summarize_messages(messages: &[ConversationMessage]) -> String {
|
|||||||
fn summarize_block(block: &ContentBlock) -> String {
|
fn summarize_block(block: &ContentBlock) -> String {
|
||||||
let raw = match block {
|
let raw = match block {
|
||||||
ContentBlock::Text { text } => text.clone(),
|
ContentBlock::Text { text } => text.clone(),
|
||||||
|
ContentBlock::Thinking { text, .. } => format!("thinking: {text}"),
|
||||||
ContentBlock::ToolUse { name, input, .. } => format!("tool_use {name}({input})"),
|
ContentBlock::ToolUse { name, input, .. } => format!("tool_use {name}({input})"),
|
||||||
ContentBlock::ToolResult {
|
ContentBlock::ToolResult {
|
||||||
tool_name,
|
tool_name,
|
||||||
@@ -258,7 +259,7 @@ fn collect_key_files(messages: &[ConversationMessage]) -> Vec<String> {
|
|||||||
.iter()
|
.iter()
|
||||||
.flat_map(|message| message.blocks.iter())
|
.flat_map(|message| message.blocks.iter())
|
||||||
.map(|block| match block {
|
.map(|block| match block {
|
||||||
ContentBlock::Text { text } => text.as_str(),
|
ContentBlock::Text { text } | ContentBlock::Thinking { text, .. } => text.as_str(),
|
||||||
ContentBlock::ToolUse { input, .. } => input.as_str(),
|
ContentBlock::ToolUse { input, .. } => input.as_str(),
|
||||||
ContentBlock::ToolResult { output, .. } => output.as_str(),
|
ContentBlock::ToolResult { output, .. } => output.as_str(),
|
||||||
})
|
})
|
||||||
@@ -280,10 +281,15 @@ fn infer_current_work(messages: &[ConversationMessage]) -> Option<String> {
|
|||||||
|
|
||||||
fn first_text_block(message: &ConversationMessage) -> Option<&str> {
|
fn first_text_block(message: &ConversationMessage) -> Option<&str> {
|
||||||
message.blocks.iter().find_map(|block| match block {
|
message.blocks.iter().find_map(|block| match block {
|
||||||
ContentBlock::Text { text } if !text.trim().is_empty() => Some(text.as_str()),
|
ContentBlock::Text { text } | ContentBlock::Thinking { text, .. }
|
||||||
|
if !text.trim().is_empty() =>
|
||||||
|
{
|
||||||
|
Some(text.as_str())
|
||||||
|
}
|
||||||
ContentBlock::ToolUse { .. }
|
ContentBlock::ToolUse { .. }
|
||||||
| ContentBlock::ToolResult { .. }
|
| ContentBlock::ToolResult { .. }
|
||||||
| ContentBlock::Text { .. } => None,
|
| ContentBlock::Text { .. }
|
||||||
|
| ContentBlock::Thinking { .. } => None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,7 +334,7 @@ fn estimate_message_tokens(message: &ConversationMessage) -> usize {
|
|||||||
.blocks
|
.blocks
|
||||||
.iter()
|
.iter()
|
||||||
.map(|block| match block {
|
.map(|block| match block {
|
||||||
ContentBlock::Text { text } => text.len() / 4 + 1,
|
ContentBlock::Text { text } | ContentBlock::Thinking { text, .. } => text.len() / 4 + 1,
|
||||||
ContentBlock::ToolUse { name, input, .. } => (name.len() + input.len()) / 4 + 1,
|
ContentBlock::ToolUse { name, input, .. } => (name.len() + input.len()) / 4 + 1,
|
||||||
ContentBlock::ToolResult {
|
ContentBlock::ToolResult {
|
||||||
tool_name, output, ..
|
tool_name, output, ..
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ pub struct ApiRequest {
|
|||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum AssistantEvent {
|
pub enum AssistantEvent {
|
||||||
TextDelta(String),
|
TextDelta(String),
|
||||||
|
ThinkingDelta(String),
|
||||||
|
ThinkingSignature(String),
|
||||||
ToolUse {
|
ToolUse {
|
||||||
id: String,
|
id: String,
|
||||||
name: String,
|
name: String,
|
||||||
@@ -247,15 +249,26 @@ fn build_assistant_message(
|
|||||||
events: Vec<AssistantEvent>,
|
events: Vec<AssistantEvent>,
|
||||||
) -> Result<(ConversationMessage, Option<TokenUsage>), RuntimeError> {
|
) -> Result<(ConversationMessage, Option<TokenUsage>), RuntimeError> {
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
|
let mut thinking = String::new();
|
||||||
|
let mut thinking_signature: Option<String> = None;
|
||||||
let mut blocks = Vec::new();
|
let mut blocks = Vec::new();
|
||||||
let mut finished = false;
|
let mut finished = false;
|
||||||
let mut usage = None;
|
let mut usage = None;
|
||||||
|
|
||||||
for event in events {
|
for event in events {
|
||||||
match event {
|
match event {
|
||||||
AssistantEvent::TextDelta(delta) => text.push_str(&delta),
|
AssistantEvent::TextDelta(delta) => {
|
||||||
|
flush_thinking_block(&mut thinking, &mut thinking_signature, &mut blocks);
|
||||||
|
text.push_str(&delta);
|
||||||
|
}
|
||||||
|
AssistantEvent::ThinkingDelta(delta) => {
|
||||||
|
flush_text_block(&mut text, &mut blocks);
|
||||||
|
thinking.push_str(&delta);
|
||||||
|
}
|
||||||
|
AssistantEvent::ThinkingSignature(signature) => thinking_signature = Some(signature),
|
||||||
AssistantEvent::ToolUse { id, name, input } => {
|
AssistantEvent::ToolUse { id, name, input } => {
|
||||||
flush_text_block(&mut text, &mut blocks);
|
flush_text_block(&mut text, &mut blocks);
|
||||||
|
flush_thinking_block(&mut thinking, &mut thinking_signature, &mut blocks);
|
||||||
blocks.push(ContentBlock::ToolUse { id, name, input });
|
blocks.push(ContentBlock::ToolUse { id, name, input });
|
||||||
}
|
}
|
||||||
AssistantEvent::Usage(value) => usage = Some(value),
|
AssistantEvent::Usage(value) => usage = Some(value),
|
||||||
@@ -266,6 +279,7 @@ fn build_assistant_message(
|
|||||||
}
|
}
|
||||||
|
|
||||||
flush_text_block(&mut text, &mut blocks);
|
flush_text_block(&mut text, &mut blocks);
|
||||||
|
flush_thinking_block(&mut thinking, &mut thinking_signature, &mut blocks);
|
||||||
|
|
||||||
if !finished {
|
if !finished {
|
||||||
return Err(RuntimeError::new(
|
return Err(RuntimeError::new(
|
||||||
@@ -290,6 +304,19 @@ fn flush_text_block(text: &mut String, blocks: &mut Vec<ContentBlock>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn flush_thinking_block(
|
||||||
|
thinking: &mut String,
|
||||||
|
signature: &mut Option<String>,
|
||||||
|
blocks: &mut Vec<ContentBlock>,
|
||||||
|
) {
|
||||||
|
if !thinking.is_empty() || signature.is_some() {
|
||||||
|
blocks.push(ContentBlock::Thinking {
|
||||||
|
text: std::mem::take(thinking),
|
||||||
|
signature: signature.take(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type ToolHandler = Box<dyn FnMut(&str) -> Result<String, ToolError>>;
|
type ToolHandler = Box<dyn FnMut(&str) -> Result<String, ToolError>>;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -325,8 +352,8 @@ impl ToolExecutor for StaticToolExecutor {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::{
|
||||||
ApiClient, ApiRequest, AssistantEvent, ConversationRuntime, RuntimeError,
|
build_assistant_message, ApiClient, ApiRequest, AssistantEvent, ConversationRuntime,
|
||||||
StaticToolExecutor,
|
RuntimeError, StaticToolExecutor,
|
||||||
};
|
};
|
||||||
use crate::compact::CompactionConfig;
|
use crate::compact::CompactionConfig;
|
||||||
use crate::permissions::{
|
use crate::permissions::{
|
||||||
@@ -502,6 +529,29 @@ mod tests {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn thinking_blocks_are_preserved_separately_from_text() {
|
||||||
|
let (message, usage) = build_assistant_message(vec![
|
||||||
|
AssistantEvent::ThinkingDelta("first ".to_string()),
|
||||||
|
AssistantEvent::ThinkingDelta("second".to_string()),
|
||||||
|
AssistantEvent::ThinkingSignature("sig-1".to_string()),
|
||||||
|
AssistantEvent::TextDelta("final".to_string()),
|
||||||
|
AssistantEvent::MessageStop,
|
||||||
|
])
|
||||||
|
.expect("assistant message should build");
|
||||||
|
|
||||||
|
assert_eq!(usage, None);
|
||||||
|
assert!(matches!(
|
||||||
|
&message.blocks[0],
|
||||||
|
ContentBlock::Thinking { text, signature }
|
||||||
|
if text == "first second" && signature.as_deref() == Some("sig-1")
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
&message.blocks[1],
|
||||||
|
ContentBlock::Text { text } if text == "final"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn reconstructs_usage_tracker_from_restored_session() {
|
fn reconstructs_usage_tracker_from_restored_session() {
|
||||||
struct SimpleApi;
|
struct SimpleApi;
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ pub enum ContentBlock {
|
|||||||
Text {
|
Text {
|
||||||
text: String,
|
text: String,
|
||||||
},
|
},
|
||||||
|
Thinking {
|
||||||
|
text: String,
|
||||||
|
signature: Option<String>,
|
||||||
|
},
|
||||||
ToolUse {
|
ToolUse {
|
||||||
id: String,
|
id: String,
|
||||||
name: String,
|
name: String,
|
||||||
@@ -257,6 +261,19 @@ impl ContentBlock {
|
|||||||
object.insert("type".to_string(), JsonValue::String("text".to_string()));
|
object.insert("type".to_string(), JsonValue::String("text".to_string()));
|
||||||
object.insert("text".to_string(), JsonValue::String(text.clone()));
|
object.insert("text".to_string(), JsonValue::String(text.clone()));
|
||||||
}
|
}
|
||||||
|
Self::Thinking { text, signature } => {
|
||||||
|
object.insert(
|
||||||
|
"type".to_string(),
|
||||||
|
JsonValue::String("thinking".to_string()),
|
||||||
|
);
|
||||||
|
object.insert("text".to_string(), JsonValue::String(text.clone()));
|
||||||
|
if let Some(signature) = signature {
|
||||||
|
object.insert(
|
||||||
|
"signature".to_string(),
|
||||||
|
JsonValue::String(signature.clone()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Self::ToolUse { id, name, input } => {
|
Self::ToolUse { id, name, input } => {
|
||||||
object.insert(
|
object.insert(
|
||||||
"type".to_string(),
|
"type".to_string(),
|
||||||
@@ -303,6 +320,13 @@ impl ContentBlock {
|
|||||||
"text" => Ok(Self::Text {
|
"text" => Ok(Self::Text {
|
||||||
text: required_string(object, "text")?,
|
text: required_string(object, "text")?,
|
||||||
}),
|
}),
|
||||||
|
"thinking" => Ok(Self::Thinking {
|
||||||
|
text: required_string(object, "text")?,
|
||||||
|
signature: object
|
||||||
|
.get("signature")
|
||||||
|
.and_then(JsonValue::as_str)
|
||||||
|
.map(ToOwned::to_owned),
|
||||||
|
}),
|
||||||
"tool_use" => Ok(Self::ToolUse {
|
"tool_use" => Ok(Self::ToolUse {
|
||||||
id: required_string(object, "id")?,
|
id: required_string(object, "id")?,
|
||||||
name: required_string(object, "name")?,
|
name: required_string(object, "name")?,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user