use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SystemPromptMeta {
#[serde(skip_serializing_if = "Option::is_none")]
pub append: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub replace: Option<String>,
}
impl SystemPromptMeta {
pub fn from_meta(meta: &serde_json::Value) -> Option<Self> {
meta.get("systemPrompt")
.and_then(|v| serde_json::from_value(v.clone()).ok())
}
pub fn is_configured(&self) -> bool {
self.append.is_some() || self.replace.is_some()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ClaudeCodeOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub resume: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", rename = "maxThinkingTokens")]
pub max_thinking_tokens: Option<u32>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ClaudeCodeMeta {
#[serde(skip_serializing_if = "Option::is_none")]
pub options: Option<ClaudeCodeOptions>,
}
impl ClaudeCodeMeta {
pub fn from_meta(meta: &serde_json::Value) -> Option<Self> {
meta.get("claudeCode")
.and_then(|v| serde_json::from_value(v.clone()).ok())
}
pub fn get_resume_session_id(&self) -> Option<&str> {
self.options.as_ref()?.resume.as_deref()
}
pub fn get_max_thinking_tokens(&self) -> Option<u32> {
self.options.as_ref()?.max_thinking_tokens
}
}
#[derive(Debug, Clone, Default)]
pub struct NewSessionMeta {
pub system_prompt: Option<SystemPromptMeta>,
pub claude_code: Option<ClaudeCodeMeta>,
pub disable_built_in_tools: bool,
pub fork_session: bool,
}
impl NewSessionMeta {
pub fn with_resume(session_id: &str) -> Self {
Self {
system_prompt: None,
claude_code: Some(ClaudeCodeMeta {
options: Some(ClaudeCodeOptions {
resume: Some(session_id.to_string()),
max_thinking_tokens: None,
}),
}),
disable_built_in_tools: false,
fork_session: false,
}
}
pub fn with_resume_and_fork(session_id: &str) -> Self {
Self {
system_prompt: None,
claude_code: Some(ClaudeCodeMeta {
options: Some(ClaudeCodeOptions {
resume: Some(session_id.to_string()),
max_thinking_tokens: None,
}),
}),
disable_built_in_tools: false,
fork_session: true,
}
}
pub fn from_request_meta(meta: Option<&serde_json::Value>) -> Self {
let Some(meta) = meta else {
return Self::default();
};
Self {
system_prompt: SystemPromptMeta::from_meta(meta),
claude_code: ClaudeCodeMeta::from_meta(meta),
disable_built_in_tools: meta
.get("disableBuiltInTools")
.and_then(|v| v.as_bool())
.unwrap_or(false),
fork_session: false,
}
}
pub fn get_system_prompt_append(&self) -> Option<&str> {
self.system_prompt.as_ref()?.append.as_deref()
}
pub fn get_system_prompt_replace(&self) -> Option<&str> {
self.system_prompt.as_ref()?.replace.as_deref()
}
pub fn get_resume_session_id(&self) -> Option<&str> {
self.claude_code.as_ref()?.get_resume_session_id()
}
pub fn get_max_thinking_tokens(&self) -> Option<u32> {
self.claude_code.as_ref()?.get_max_thinking_tokens()
}
pub fn should_resume(&self) -> bool {
self.get_resume_session_id().is_some()
}
pub fn is_fork(&self) -> bool {
self.fork_session
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_system_prompt_meta_parse() {
let meta = json!({
"systemPrompt": {
"append": "Please respond in Chinese"
}
});
let parsed = SystemPromptMeta::from_meta(&meta).unwrap();
assert_eq!(parsed.append, Some("Please respond in Chinese".to_string()));
assert!(parsed.replace.is_none());
assert!(parsed.is_configured());
}
#[test]
fn test_claude_code_meta_parse() {
let meta = json!({
"claudeCode": {
"options": {
"resume": "session-uuid-12345"
}
}
});
let parsed = ClaudeCodeMeta::from_meta(&meta).unwrap();
assert_eq!(parsed.get_resume_session_id(), Some("session-uuid-12345"));
}
#[test]
fn test_claude_code_meta_with_thinking_tokens() {
let meta = json!({
"claudeCode": {
"options": {
"resume": "session-uuid-12345",
"maxThinkingTokens": 4096
}
}
});
let parsed = ClaudeCodeMeta::from_meta(&meta).unwrap();
assert_eq!(parsed.get_resume_session_id(), Some("session-uuid-12345"));
assert_eq!(parsed.get_max_thinking_tokens(), Some(4096));
}
#[test]
fn test_new_session_meta_full() {
let meta = json!({
"systemPrompt": {
"append": "Be concise"
},
"claudeCode": {
"options": {
"resume": "abc-123",
"maxThinkingTokens": 8000
}
},
"disableBuiltInTools": true
});
let parsed = NewSessionMeta::from_request_meta(Some(&meta));
assert_eq!(parsed.get_system_prompt_append(), Some("Be concise"));
assert_eq!(parsed.get_resume_session_id(), Some("abc-123"));
assert_eq!(parsed.get_max_thinking_tokens(), Some(8000));
assert!(parsed.disable_built_in_tools);
assert!(parsed.should_resume());
}
#[test]
fn test_new_session_meta_empty() {
let parsed = NewSessionMeta::from_request_meta(None);
assert!(parsed.system_prompt.is_none());
assert!(parsed.claude_code.is_none());
assert!(!parsed.disable_built_in_tools);
assert!(!parsed.should_resume());
}
#[test]
fn test_new_session_meta_partial() {
let meta = json!({
"systemPrompt": {
"replace": "You are a helpful assistant"
}
});
let parsed = NewSessionMeta::from_request_meta(Some(&meta));
assert_eq!(
parsed.get_system_prompt_replace(),
Some("You are a helpful assistant")
);
assert!(parsed.get_system_prompt_append().is_none());
assert!(parsed.get_resume_session_id().is_none());
}
#[test]
fn test_new_session_meta_with_resume() {
let meta = NewSessionMeta::with_resume("session-abc-123");
assert_eq!(meta.get_resume_session_id(), Some("session-abc-123"));
assert!(meta.should_resume());
assert!(meta.system_prompt.is_none());
assert!(!meta.disable_built_in_tools);
}
}