Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions crates/tinyagents-session/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,9 @@ pub use tinyagents_harness::error::{Result, TinyAgentsError};

pub use ops::{
DEFAULT_FTS_SNIPPET_BYTES, fts_snippet_bytes, get_session, list_children, list_messages,
list_sessions, list_tool_calls, mark_interrupted, record_message, record_session_end,
record_session_start, record_tool_call, search_sessions, set_fts_snippet_bytes,
list_sessions, list_tool_calls, mark_interrupted, record_message,
record_message_with_reasoning, record_session_end, record_session_start, record_tool_call,
Comment thread
senamakel marked this conversation as resolved.
search_sessions, set_fts_snippet_bytes,
};
pub use retention::{
RetentionReport, apply_retention, prune_run_events_before, prune_run_telemetry_before,
Expand Down
4 changes: 3 additions & 1 deletion crates/tinyagents-session/src/migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ pub(super) const MIGRATIONS: &[&str] = &[
CREATE INDEX IF NOT EXISTS idx_agent_teams_updated ON agent_teams(updated_at);
CREATE INDEX IF NOT EXISTS idx_agent_team_tasks_order
ON agent_team_tasks(team_id, order_index, created_at);",
// ---- 4: retain hidden assistant reasoning ---------------------------
"ALTER TABLE session_messages ADD COLUMN reasoning_content TEXT;",
Comment thread
senamakel marked this conversation as resolved.
];

/// Applies every migration newer than the database's recorded schema version.
Expand Down Expand Up @@ -325,7 +327,7 @@ mod test {
fn migration_list_is_append_only() {
assert_eq!(
MIGRATIONS.len(),
4,
5,
"MIGRATIONS is append-only — adding one is fine, reordering or \
deleting one silently re-numbers every later migration"
);
Expand Down
48 changes: 40 additions & 8 deletions crates/tinyagents-session/src/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,36 @@ pub fn record_message(
input_tokens: Option<u64>,
output_tokens: Option<u64>,
cost_usd: Option<f64>,
) -> Result<i64> {
record_message_with_reasoning(
workspace_dir,
session_id,
role,
content,
None,
model,
input_tokens,
output_tokens,
cost_usd,
)
}

/// Record a session message together with provider-exposed hidden reasoning.
///
/// The historical [`record_message`] API deliberately remains visible-text
/// only. Hosts that retain a structured assistant message should use this
/// variant so a tool-call turn does not discard its thinking trace.
#[allow(clippy::too_many_arguments)]
pub fn record_message_with_reasoning(
workspace_dir: &Path,
session_id: &str,
role: &str,
content: &str,
reasoning_content: Option<&str>,
model: Option<&str>,
input_tokens: Option<u64>,
output_tokens: Option<u64>,
cost_usd: Option<f64>,
) -> Result<i64> {
let now = Utc::now();
tinyagents_tracing::trace!(
Expand All @@ -149,13 +179,14 @@ pub fn record_message(
with_transaction(workspace_dir, |conn| {
conn.execute(
"INSERT INTO session_messages (
session_id, role, content, model,
session_id, role, content, reasoning_content, model,
input_tokens, output_tokens, cost_usd, created_at
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)",
params![
session_id,
role,
content,
reasoning_content.filter(|value| !value.trim().is_empty()),
model,
input_tokens.map(|v| v as i64),
output_tokens.map(|v| v as i64),
Expand Down Expand Up @@ -456,7 +487,7 @@ pub fn list_messages(
with_connection(workspace_dir, |conn| {
let lim = limit.unwrap_or(200).min(1000) as i64;
let mut stmt = conn.prepare(
"SELECT id, session_id, role, content, model,
"SELECT id, session_id, role, content, reasoning_content, model,
input_tokens, output_tokens, cost_usd, created_at
FROM session_messages
WHERE session_id = ?1
Expand All @@ -470,11 +501,12 @@ pub fn list_messages(
session_id: row.get(1)?,
role: row.get(2)?,
content: row.get(3)?,
model: row.get(4)?,
input_tokens: row.get::<_, Option<i64>>(5)?.map(|v| v as u64),
output_tokens: row.get::<_, Option<i64>>(6)?.map(|v| v as u64),
cost_usd: row.get(7)?,
created_at: parse_rfc3339(&row.get::<_, String>(8)?)
reasoning_content: row.get(4)?,
model: row.get(5)?,
input_tokens: row.get::<_, Option<i64>>(6)?.map(|v| v as u64),
output_tokens: row.get::<_, Option<i64>>(7)?.map(|v| v as u64),
cost_usd: row.get(8)?,
created_at: parse_rfc3339(&row.get::<_, String>(9)?)
.map_err(sql_conversion_error)?,
})
})?;
Expand Down
40 changes: 40 additions & 0 deletions crates/tinyagents-session/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,46 @@ fn public_session_operations_round_trip_every_record_kind() {
assert!(ended.ended_at.is_some());
}

#[test]
fn assistant_reasoning_round_trips_separately_from_visible_content() {
let dir = tempfile::tempdir().unwrap();
let workspace = dir.path();
record_session_start(
workspace,
"reasoning-session",
"agent",
"Agent",
"reasoning-session",
None,
None,
None,
Some("reasoning-model"),
None,
)
.unwrap();

record_message_with_reasoning(
workspace,
"reasoning-session",
"assistant",
"I will inspect the repository.",
Some("First identify the relevant files."),
Some("reasoning-model"),
None,
None,
None,
)
.unwrap();

let messages = list_messages(workspace, "reasoning-session", None).unwrap();
assert_eq!(messages.len(), 1);
assert_eq!(messages[0].content, "I will inspect the repository.");
assert_eq!(
messages[0].reasoning_content.as_deref(),
Some("First identify the relevant files.")
);
}

#[test]
fn public_listing_filters_and_caps_results() {
let dir = tempfile::tempdir().unwrap();
Expand Down
4 changes: 4 additions & 0 deletions crates/tinyagents-session/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ pub struct SessionMessage {
pub session_id: String,
pub role: String,
pub content: String,
/// Hidden model reasoning, when the provider exposed it. Kept separate
/// from visible `content` so search/UI consumers never mistake it for an
/// assistant reply.
pub reasoning_content: Option<String>,
pub model: Option<String>,
pub input_tokens: Option<u64>,
pub output_tokens: Option<u64>,
Expand Down