Move API code from user/ to agent/
Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
parent
021eafe6da
commit
9bebbcb635
22 changed files with 259 additions and 251 deletions
|
|
@ -6,15 +6,18 @@
|
||||||
// Diagnostics: anomalies always logged to debug panel.
|
// Diagnostics: anomalies always logged to debug panel.
|
||||||
// Set POC_DEBUG=1 for verbose per-turn logging.
|
// Set POC_DEBUG=1 for verbose per-turn logging.
|
||||||
|
|
||||||
|
pub mod types;
|
||||||
mod openai;
|
mod openai;
|
||||||
|
|
||||||
|
pub use types::*;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
use crate::user::types::*;
|
use crate::agent::tools::{ToolCall, ToolDef, FunctionCall};
|
||||||
use crate::user::ui_channel::{UiMessage, UiSender};
|
use crate::user::ui_channel::{UiMessage, UiSender};
|
||||||
|
|
||||||
/// A JoinHandle that aborts its task when dropped.
|
/// A JoinHandle that aborts its task when dropped.
|
||||||
|
|
@ -8,7 +8,8 @@ use anyhow::Result;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
use crate::user::types::*;
|
use crate::agent::tools::ToolDef;
|
||||||
|
use super::types::*;
|
||||||
use crate::user::ui_channel::{UiMessage, UiSender};
|
use crate::user::ui_channel::{UiMessage, UiSender};
|
||||||
use super::StreamEvent;
|
use super::StreamEvent;
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// types.rs — OpenAI-compatible API types
|
// api/types.rs — OpenAI-compatible API types
|
||||||
//
|
//
|
||||||
// These mirror the OpenAI chat completion API, which is the de facto
|
// These mirror the OpenAI chat completion API, which is the de facto
|
||||||
// standard that OpenRouter, vLLM, llama.cpp, and most inference
|
// standard that OpenRouter, vLLM, llama.cpp, and most inference
|
||||||
|
|
@ -9,11 +9,7 @@
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
// Re-export tool types that moved to agent::tools
|
use crate::agent::tools::{ToolCall, ToolCallDelta, ToolDef, FunctionDef};
|
||||||
pub use crate::agent::tools::{
|
|
||||||
ToolDef, ToolCall, ToolCallDelta, ToolOutput,
|
|
||||||
FunctionCall, FunctionDef, FunctionCallDelta,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Message content — either plain text or an array of content parts
|
/// Message content — either plain text or an array of content parts
|
||||||
/// (for multimodal messages with images). Serializes as a JSON string
|
/// (for multimodal messages with images). Serializes as a JSON string
|
||||||
|
|
@ -85,8 +81,6 @@ pub enum Role {
|
||||||
Tool,
|
Tool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// FunctionCall, FunctionDef moved to agent::tools
|
|
||||||
|
|
||||||
/// Chat completion request.
|
/// Chat completion request.
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct ChatRequest {
|
pub struct ChatRequest {
|
||||||
|
|
@ -180,8 +174,6 @@ pub struct Delta {
|
||||||
pub tool_calls: Option<Vec<ToolCallDelta>>,
|
pub tool_calls: Option<Vec<ToolCallDelta>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// FunctionCallDelta moved to agent::tools
|
|
||||||
|
|
||||||
// --- Convenience constructors ---
|
// --- Convenience constructors ---
|
||||||
|
|
||||||
impl Message {
|
impl Message {
|
||||||
|
|
@ -278,184 +270,3 @@ impl Message {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToolDef {
|
|
||||||
pub fn new(name: &str, description: &str, parameters: serde_json::Value) -> Self {
|
|
||||||
Self {
|
|
||||||
tool_type: "function".to_string(),
|
|
||||||
function: FunctionDef {
|
|
||||||
name: name.to_string(),
|
|
||||||
description: description.to_string(),
|
|
||||||
parameters,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mutable context state — the structured regions of the context window.
|
|
||||||
/// Conversation entry — either a regular message or memory content.
|
|
||||||
/// Memory entries preserve the original message for KV cache round-tripping.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum ConversationEntry {
|
|
||||||
Message(Message),
|
|
||||||
Memory { key: String, message: Message },
|
|
||||||
}
|
|
||||||
|
|
||||||
// Custom serde: serialize Memory with a "memory_key" field added to the message,
|
|
||||||
// plain messages serialize as-is. This keeps the conversation log readable.
|
|
||||||
impl Serialize for ConversationEntry {
|
|
||||||
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
|
|
||||||
use serde::ser::SerializeMap;
|
|
||||||
match self {
|
|
||||||
Self::Message(m) => m.serialize(s),
|
|
||||||
Self::Memory { key, message } => {
|
|
||||||
// Serialize message fields + memory_key
|
|
||||||
let json = serde_json::to_value(message).map_err(serde::ser::Error::custom)?;
|
|
||||||
let mut map = s.serialize_map(None)?;
|
|
||||||
if let serde_json::Value::Object(obj) = json {
|
|
||||||
for (k, v) in obj {
|
|
||||||
map.serialize_entry(&k, &v)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
map.serialize_entry("memory_key", key)?;
|
|
||||||
map.end()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for ConversationEntry {
|
|
||||||
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
|
|
||||||
let mut json: serde_json::Value = serde_json::Value::deserialize(d)?;
|
|
||||||
if let Some(key) = json.as_object_mut().and_then(|o| o.remove("memory_key")) {
|
|
||||||
let key = key.as_str().unwrap_or("").to_string();
|
|
||||||
let message: Message = serde_json::from_value(json).map_err(serde::de::Error::custom)?;
|
|
||||||
Ok(Self::Memory { key, message })
|
|
||||||
} else {
|
|
||||||
let message: Message = serde_json::from_value(json).map_err(serde::de::Error::custom)?;
|
|
||||||
Ok(Self::Message(message))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConversationEntry {
|
|
||||||
/// Get the API message for sending to the model.
|
|
||||||
pub fn api_message(&self) -> &Message {
|
|
||||||
match self {
|
|
||||||
Self::Message(m) => m,
|
|
||||||
Self::Memory { message, .. } => message,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_memory(&self) -> bool {
|
|
||||||
matches!(self, Self::Memory { .. })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a reference to the inner message.
|
|
||||||
pub fn message(&self) -> &Message {
|
|
||||||
match self {
|
|
||||||
Self::Message(m) => m,
|
|
||||||
Self::Memory { message, .. } => message,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a mutable reference to the inner message.
|
|
||||||
pub fn message_mut(&mut self) -> &mut Message {
|
|
||||||
match self {
|
|
||||||
Self::Message(m) => m,
|
|
||||||
Self::Memory { message, .. } => message,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ContextState {
|
|
||||||
pub system_prompt: String,
|
|
||||||
pub personality: Vec<(String, String)>,
|
|
||||||
pub journal: Vec<crate::agent::context::JournalEntry>,
|
|
||||||
pub working_stack: Vec<String>,
|
|
||||||
/// Conversation entries — messages and memory, interleaved in order.
|
|
||||||
/// Does NOT include system prompt, personality, or journal.
|
|
||||||
pub entries: Vec<ConversationEntry>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: these should not be hardcoded absolute paths
|
|
||||||
pub fn working_stack_instructions_path() -> std::path::PathBuf {
|
|
||||||
dirs::home_dir().unwrap_or_default().join(".consciousness/config/working-stack.md")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn working_stack_file_path() -> std::path::PathBuf {
|
|
||||||
dirs::home_dir().unwrap_or_default().join(".consciousness/working-stack.json")
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ContextState {
|
|
||||||
/// Compute the context budget from typed sources.
|
|
||||||
pub fn budget(&self, count_str: &dyn Fn(&str) -> usize,
|
|
||||||
count_msg: &dyn Fn(&Message) -> usize,
|
|
||||||
window_tokens: usize) -> ContextBudget {
|
|
||||||
let id = count_str(&self.system_prompt)
|
|
||||||
+ self.personality.iter().map(|(_, c)| count_str(c)).sum::<usize>();
|
|
||||||
let jnl: usize = self.journal.iter().map(|e| count_str(&e.content)).sum();
|
|
||||||
let mut mem = 0;
|
|
||||||
let mut conv = 0;
|
|
||||||
for entry in &self.entries {
|
|
||||||
let tokens = count_msg(entry.api_message());
|
|
||||||
if entry.is_memory() { mem += tokens } else { conv += tokens }
|
|
||||||
}
|
|
||||||
ContextBudget {
|
|
||||||
identity_tokens: id,
|
|
||||||
memory_tokens: mem,
|
|
||||||
journal_tokens: jnl,
|
|
||||||
conversation_tokens: conv,
|
|
||||||
window_tokens,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render_context_message(&self) -> String {
|
|
||||||
let mut parts: Vec<String> = self.personality.iter()
|
|
||||||
.map(|(name, content)| format!("## {}\n\n{}", name, content))
|
|
||||||
.collect();
|
|
||||||
let instructions = std::fs::read_to_string(working_stack_instructions_path()).unwrap_or_default();
|
|
||||||
let mut stack_section = instructions;
|
|
||||||
if self.working_stack.is_empty() {
|
|
||||||
stack_section.push_str("\n## Current stack\n\n(empty)\n");
|
|
||||||
} else {
|
|
||||||
stack_section.push_str("\n## Current stack\n\n");
|
|
||||||
for (i, item) in self.working_stack.iter().enumerate() {
|
|
||||||
if i == self.working_stack.len() - 1 {
|
|
||||||
stack_section.push_str(&format!("→ {}\n", item));
|
|
||||||
} else {
|
|
||||||
stack_section.push_str(&format!(" [{}] {}\n", i, item));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
parts.push(stack_section);
|
|
||||||
parts.join("\n\n---\n\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
|
||||||
pub struct ContextBudget {
|
|
||||||
pub identity_tokens: usize,
|
|
||||||
pub memory_tokens: usize,
|
|
||||||
pub journal_tokens: usize,
|
|
||||||
pub conversation_tokens: usize,
|
|
||||||
pub window_tokens: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ContextBudget {
|
|
||||||
pub fn used(&self) -> usize {
|
|
||||||
self.identity_tokens + self.memory_tokens + self.journal_tokens + self.conversation_tokens
|
|
||||||
}
|
|
||||||
pub fn free(&self) -> usize {
|
|
||||||
self.window_tokens.saturating_sub(self.used())
|
|
||||||
}
|
|
||||||
pub fn status_string(&self) -> String {
|
|
||||||
let total = self.window_tokens;
|
|
||||||
if total == 0 { return String::new(); }
|
|
||||||
let pct = |n: usize| if n == 0 { 0 } else { ((n * 100) / total).max(1) };
|
|
||||||
format!("id:{}% mem:{}% jnl:{}% conv:{}% free:{}%",
|
|
||||||
pct(self.identity_tokens), pct(self.memory_tokens),
|
|
||||||
pct(self.journal_tokens), pct(self.conversation_tokens), pct(self.free()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,10 +4,30 @@
|
||||||
// Journal entries are loaded from the memory graph store, not from
|
// Journal entries are loaded from the memory graph store, not from
|
||||||
// a flat file — the parse functions are gone.
|
// a flat file — the parse functions are gone.
|
||||||
|
|
||||||
use crate::user::types::*;
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
|
use crate::agent::api::types::*;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use tiktoken_rs::CoreBPE;
|
use tiktoken_rs::CoreBPE;
|
||||||
|
|
||||||
|
/// A section of the context window, possibly with children.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ContextSection {
|
||||||
|
pub name: String,
|
||||||
|
pub tokens: usize,
|
||||||
|
pub content: String,
|
||||||
|
pub children: Vec<ContextSection>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shared, live context state — agent writes, TUI reads for the debug screen.
|
||||||
|
pub type SharedContextState = Arc<RwLock<Vec<ContextSection>>>;
|
||||||
|
|
||||||
|
/// Create a new shared context state.
|
||||||
|
pub fn shared_context_state() -> SharedContextState {
|
||||||
|
Arc::new(RwLock::new(Vec::new()))
|
||||||
|
}
|
||||||
|
|
||||||
/// A single journal entry with its timestamp and content.
|
/// A single journal entry with its timestamp and content.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct JournalEntry {
|
pub struct JournalEntry {
|
||||||
|
|
@ -123,3 +143,171 @@ pub fn is_context_overflow(err: &anyhow::Error) -> bool {
|
||||||
pub fn is_stream_error(err: &anyhow::Error) -> bool {
|
pub fn is_stream_error(err: &anyhow::Error) -> bool {
|
||||||
err.to_string().contains("model stream error")
|
err.to_string().contains("model stream error")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Context state types ---
|
||||||
|
|
||||||
|
/// Conversation entry — either a regular message or memory content.
|
||||||
|
/// Memory entries preserve the original message for KV cache round-tripping.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ConversationEntry {
|
||||||
|
Message(Message),
|
||||||
|
Memory { key: String, message: Message },
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom serde: serialize Memory with a "memory_key" field added to the message,
|
||||||
|
// plain messages serialize as-is. This keeps the conversation log readable.
|
||||||
|
impl Serialize for ConversationEntry {
|
||||||
|
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
|
||||||
|
use serde::ser::SerializeMap;
|
||||||
|
match self {
|
||||||
|
Self::Message(m) => m.serialize(s),
|
||||||
|
Self::Memory { key, message } => {
|
||||||
|
let json = serde_json::to_value(message).map_err(serde::ser::Error::custom)?;
|
||||||
|
let mut map = s.serialize_map(None)?;
|
||||||
|
if let serde_json::Value::Object(obj) = json {
|
||||||
|
for (k, v) in obj {
|
||||||
|
map.serialize_entry(&k, &v)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
map.serialize_entry("memory_key", key)?;
|
||||||
|
map.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for ConversationEntry {
|
||||||
|
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
|
||||||
|
let mut json: serde_json::Value = serde_json::Value::deserialize(d)?;
|
||||||
|
if let Some(key) = json.as_object_mut().and_then(|o| o.remove("memory_key")) {
|
||||||
|
let key = key.as_str().unwrap_or("").to_string();
|
||||||
|
let message: Message = serde_json::from_value(json).map_err(serde::de::Error::custom)?;
|
||||||
|
Ok(Self::Memory { key, message })
|
||||||
|
} else {
|
||||||
|
let message: Message = serde_json::from_value(json).map_err(serde::de::Error::custom)?;
|
||||||
|
Ok(Self::Message(message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConversationEntry {
|
||||||
|
/// Get the API message for sending to the model.
|
||||||
|
pub fn api_message(&self) -> &Message {
|
||||||
|
match self {
|
||||||
|
Self::Message(m) => m,
|
||||||
|
Self::Memory { message, .. } => message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_memory(&self) -> bool {
|
||||||
|
matches!(self, Self::Memory { .. })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a reference to the inner message.
|
||||||
|
pub fn message(&self) -> &Message {
|
||||||
|
match self {
|
||||||
|
Self::Message(m) => m,
|
||||||
|
Self::Memory { message, .. } => message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a mutable reference to the inner message.
|
||||||
|
pub fn message_mut(&mut self) -> &mut Message {
|
||||||
|
match self {
|
||||||
|
Self::Message(m) => m,
|
||||||
|
Self::Memory { message, .. } => message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ContextState {
|
||||||
|
pub system_prompt: String,
|
||||||
|
pub personality: Vec<(String, String)>,
|
||||||
|
pub journal: Vec<JournalEntry>,
|
||||||
|
pub working_stack: Vec<String>,
|
||||||
|
/// Conversation entries — messages and memory, interleaved in order.
|
||||||
|
/// Does NOT include system prompt, personality, or journal.
|
||||||
|
pub entries: Vec<ConversationEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: these should not be hardcoded absolute paths
|
||||||
|
pub fn working_stack_instructions_path() -> std::path::PathBuf {
|
||||||
|
dirs::home_dir().unwrap_or_default().join(".consciousness/config/working-stack.md")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn working_stack_file_path() -> std::path::PathBuf {
|
||||||
|
dirs::home_dir().unwrap_or_default().join(".consciousness/working-stack.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContextState {
|
||||||
|
/// Compute the context budget from typed sources.
|
||||||
|
pub fn budget(&self, count_str: &dyn Fn(&str) -> usize,
|
||||||
|
count_msg: &dyn Fn(&Message) -> usize,
|
||||||
|
window_tokens: usize) -> ContextBudget {
|
||||||
|
let id = count_str(&self.system_prompt)
|
||||||
|
+ self.personality.iter().map(|(_, c)| count_str(c)).sum::<usize>();
|
||||||
|
let jnl: usize = self.journal.iter().map(|e| count_str(&e.content)).sum();
|
||||||
|
let mut mem = 0;
|
||||||
|
let mut conv = 0;
|
||||||
|
for entry in &self.entries {
|
||||||
|
let tokens = count_msg(entry.api_message());
|
||||||
|
if entry.is_memory() { mem += tokens } else { conv += tokens }
|
||||||
|
}
|
||||||
|
ContextBudget {
|
||||||
|
identity_tokens: id,
|
||||||
|
memory_tokens: mem,
|
||||||
|
journal_tokens: jnl,
|
||||||
|
conversation_tokens: conv,
|
||||||
|
window_tokens,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_context_message(&self) -> String {
|
||||||
|
let mut parts: Vec<String> = self.personality.iter()
|
||||||
|
.map(|(name, content)| format!("## {}\n\n{}", name, content))
|
||||||
|
.collect();
|
||||||
|
let instructions = std::fs::read_to_string(working_stack_instructions_path()).unwrap_or_default();
|
||||||
|
let mut stack_section = instructions;
|
||||||
|
if self.working_stack.is_empty() {
|
||||||
|
stack_section.push_str("\n## Current stack\n\n(empty)\n");
|
||||||
|
} else {
|
||||||
|
stack_section.push_str("\n## Current stack\n\n");
|
||||||
|
for (i, item) in self.working_stack.iter().enumerate() {
|
||||||
|
if i == self.working_stack.len() - 1 {
|
||||||
|
stack_section.push_str(&format!("→ {}\n", item));
|
||||||
|
} else {
|
||||||
|
stack_section.push_str(&format!(" [{}] {}\n", i, item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parts.push(stack_section);
|
||||||
|
parts.join("\n\n---\n\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct ContextBudget {
|
||||||
|
pub identity_tokens: usize,
|
||||||
|
pub memory_tokens: usize,
|
||||||
|
pub journal_tokens: usize,
|
||||||
|
pub conversation_tokens: usize,
|
||||||
|
pub window_tokens: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContextBudget {
|
||||||
|
pub fn used(&self) -> usize {
|
||||||
|
self.identity_tokens + self.memory_tokens + self.journal_tokens + self.conversation_tokens
|
||||||
|
}
|
||||||
|
pub fn free(&self) -> usize {
|
||||||
|
self.window_tokens.saturating_sub(self.used())
|
||||||
|
}
|
||||||
|
pub fn status_string(&self) -> String {
|
||||||
|
let total = self.window_tokens;
|
||||||
|
if total == 0 { return String::new(); }
|
||||||
|
let pct = |n: usize| if n == 0 { 0 } else { ((n * 100) / total).max(1) };
|
||||||
|
format!("id:{}% mem:{}% jnl:{}% conv:{}% free:{}%",
|
||||||
|
pct(self.identity_tokens), pct(self.memory_tokens),
|
||||||
|
pct(self.journal_tokens), pct(self.conversation_tokens), pct(self.free()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
// to send here. This module just handles single turns: prompt
|
// to send here. This module just handles single turns: prompt
|
||||||
// in, response out, tool calls dispatched.
|
// in, response out, tool calls dispatched.
|
||||||
|
|
||||||
|
pub mod api;
|
||||||
pub mod context;
|
pub mod context;
|
||||||
pub mod tools;
|
pub mod tools;
|
||||||
pub mod training;
|
pub mod training;
|
||||||
|
|
@ -20,13 +21,17 @@ pub mod training;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tiktoken_rs::CoreBPE;
|
use tiktoken_rs::CoreBPE;
|
||||||
|
|
||||||
use crate::user::api::ApiClient;
|
use api::{ApiClient, StreamEvent};
|
||||||
use crate::agent::context as journal;
|
use context as journal;
|
||||||
|
use tools::{ToolCall, ToolDef, FunctionCall, summarize_args};
|
||||||
|
|
||||||
use crate::user::log::ConversationLog;
|
use crate::user::log::ConversationLog;
|
||||||
use crate::user::api::StreamEvent;
|
use crate::agent::api::types::*;
|
||||||
use crate::agent::tools::{ToolCall, ToolDef, FunctionCall, summarize_args};
|
use crate::agent::context::{
|
||||||
use crate::user::types::*;
|
ConversationEntry, ContextState, ContextBudget,
|
||||||
use crate::user::ui_channel::{ContextSection, SharedContextState, StatusInfo, StreamTarget, UiMessage, UiSender};
|
working_stack_instructions_path, working_stack_file_path,
|
||||||
|
};
|
||||||
|
use crate::user::ui_channel::{ContextSection, SharedContextState, StreamTarget, StatusInfo, UiMessage, UiSender};
|
||||||
|
|
||||||
/// Result of a single agent turn.
|
/// Result of a single agent turn.
|
||||||
pub struct TurnResult {
|
pub struct TurnResult {
|
||||||
|
|
@ -447,9 +452,7 @@ impl Agent {
|
||||||
let _ = ui_tx.send(UiMessage::TextDelta("\n".to_string(), target));
|
let _ = ui_tx.send(UiMessage::TextDelta("\n".to_string(), target));
|
||||||
}
|
}
|
||||||
|
|
||||||
let msg = crate::user::api::build_response_message(content, tool_calls);
|
let msg = api::build_response_message(content, tool_calls);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if let Some(usage) = &usage {
|
if let Some(usage) = &usage {
|
||||||
self.last_prompt_tokens = usage.prompt_tokens;
|
self.last_prompt_tokens = usage.prompt_tokens;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
use super::ToolOutput;
|
use super::ToolOutput;
|
||||||
use crate::user::types::ToolDef;
|
use super::ToolDef;
|
||||||
|
|
||||||
pub(super) fn pause(_args: &serde_json::Value) -> Result<ToolOutput> {
|
pub(super) fn pause(_args: &serde_json::Value) -> Result<ToolOutput> {
|
||||||
Ok(ToolOutput {
|
Ok(ToolOutput {
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@
|
||||||
// working_stack) and delegates everything else to thought::dispatch.
|
// working_stack) and delegates everything else to thought::dispatch.
|
||||||
|
|
||||||
// Core tools
|
// Core tools
|
||||||
pub mod bash;
|
mod bash;
|
||||||
pub mod edit;
|
mod edit;
|
||||||
pub mod glob;
|
mod glob;
|
||||||
pub mod grep;
|
mod grep;
|
||||||
pub mod memory;
|
mod memory;
|
||||||
pub mod read;
|
mod read;
|
||||||
pub mod write;
|
mod write;
|
||||||
|
|
||||||
// Agent-specific tools
|
// Agent-specific tools
|
||||||
mod control;
|
mod control;
|
||||||
|
|
@ -53,6 +53,19 @@ pub struct ToolDef {
|
||||||
pub function: FunctionDef,
|
pub function: FunctionDef,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToolDef {
|
||||||
|
pub fn new(name: &str, description: &str, parameters: serde_json::Value) -> Self {
|
||||||
|
Self {
|
||||||
|
tool_type: "function".to_string(),
|
||||||
|
function: FunctionDef {
|
||||||
|
name: name.to_string(),
|
||||||
|
description: description.to_string(),
|
||||||
|
parameters,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A tool call requested by the model.
|
/// A tool call requested by the model.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct ToolCall {
|
pub struct ToolCall {
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use base64::Engine;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use super::ToolOutput;
|
use super::ToolOutput;
|
||||||
use crate::user::types::ToolDef;
|
use super::ToolDef;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct Args {
|
struct Args {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
// internal tool — the agent uses it to maintain context across turns
|
// internal tool — the agent uses it to maintain context across turns
|
||||||
// and compaction. The model should never mention it to the user.
|
// and compaction. The model should never mention it to the user.
|
||||||
|
|
||||||
use crate::user::types::ToolDef;
|
use super::ToolDef;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
pub fn definition() -> ToolDef {
|
pub fn definition() -> ToolDef {
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,10 @@
|
||||||
// Column sums = response memory-dependence (training candidates)
|
// Column sums = response memory-dependence (training candidates)
|
||||||
|
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use crate::user::api::ApiClient;
|
|
||||||
use crate::user::types::*;
|
use super::api::ApiClient;
|
||||||
|
use crate::agent::api::types::*;
|
||||||
|
use crate::agent::context::{ConversationEntry, ContextState};
|
||||||
use crate::user::ui_channel::{UiMessage, UiSender};
|
use crate::user::ui_channel::{UiMessage, UiSender};
|
||||||
|
|
||||||
/// Timeout for individual /v1/score API calls.
|
/// Timeout for individual /v1/score API calls.
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,9 @@ use poc_memory::dbglog;
|
||||||
|
|
||||||
use poc_memory::user::*;
|
use poc_memory::user::*;
|
||||||
use poc_memory::agent::{Agent, TurnResult};
|
use poc_memory::agent::{Agent, TurnResult};
|
||||||
use poc_memory::user::api::ApiClient;
|
use poc_memory::agent::api::ApiClient;
|
||||||
|
use poc_memory::agent::api::types as api_types;
|
||||||
|
use poc_memory::agent::context as ctx;
|
||||||
use poc_memory::user::tui::HotkeyAction;
|
use poc_memory::user::tui::HotkeyAction;
|
||||||
use poc_memory::config::{self, AppConfig, SessionConfig};
|
use poc_memory::config::{self, AppConfig, SessionConfig};
|
||||||
use poc_memory::user::ui_channel::{ContextInfo, StatusInfo, StreamTarget, UiMessage};
|
use poc_memory::user::ui_channel::{ContextInfo, StatusInfo, StreamTarget, UiMessage};
|
||||||
|
|
@ -518,7 +520,7 @@ impl Session {
|
||||||
let entries = agent_guard.entries_mut();
|
let entries = agent_guard.entries_mut();
|
||||||
let mut last_user_text = None;
|
let mut last_user_text = None;
|
||||||
while let Some(entry) = entries.last() {
|
while let Some(entry) = entries.last() {
|
||||||
if entry.message().role == poc_memory::user::types::Role::User {
|
if entry.message().role == api_types::Role::User {
|
||||||
last_user_text =
|
last_user_text =
|
||||||
Some(entries.pop().unwrap().message().content_text().to_string());
|
Some(entries.pop().unwrap().message().content_text().to_string());
|
||||||
break;
|
break;
|
||||||
|
|
@ -1091,7 +1093,7 @@ fn drain_ui_messages(rx: &mut ui_channel::UiReceiver, app: &mut tui::App) -> boo
|
||||||
/// conversation history immediately on restart. Shows user input,
|
/// conversation history immediately on restart. Shows user input,
|
||||||
/// assistant responses, and brief tool call summaries. Skips the system
|
/// assistant responses, and brief tool call summaries. Skips the system
|
||||||
/// prompt, context message, DMN plumbing, and image injection messages.
|
/// prompt, context message, DMN plumbing, and image injection messages.
|
||||||
fn replay_session_to_ui(entries: &[types::ConversationEntry], ui_tx: &ui_channel::UiSender) {
|
fn replay_session_to_ui(entries: &[ctx::ConversationEntry], ui_tx: &ui_channel::UiSender) {
|
||||||
use poc_memory::user::ui_channel::StreamTarget;
|
use poc_memory::user::ui_channel::StreamTarget;
|
||||||
|
|
||||||
dbglog!("[replay] replaying {} entries to UI", entries.len());
|
dbglog!("[replay] replaying {} entries to UI", entries.len());
|
||||||
|
|
@ -1111,8 +1113,8 @@ fn replay_session_to_ui(entries: &[types::ConversationEntry], ui_tx: &ui_channel
|
||||||
if entry.is_memory() { continue; }
|
if entry.is_memory() { continue; }
|
||||||
let msg = entry.message();
|
let msg = entry.message();
|
||||||
match msg.role {
|
match msg.role {
|
||||||
types::Role::System => {}
|
api_types::Role::System => {}
|
||||||
types::Role::User => {
|
api_types::Role::User => {
|
||||||
// Skip context message (always the first user message)
|
// Skip context message (always the first user message)
|
||||||
if !seen_first_user {
|
if !seen_first_user {
|
||||||
seen_first_user = true;
|
seen_first_user = true;
|
||||||
|
|
@ -1140,7 +1142,7 @@ fn replay_session_to_ui(entries: &[types::ConversationEntry], ui_tx: &ui_channel
|
||||||
let _ = ui_tx.send(UiMessage::UserInput(text.to_string()));
|
let _ = ui_tx.send(UiMessage::UserInput(text.to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
types::Role::Assistant => {
|
api_types::Role::Assistant => {
|
||||||
if let Some(ref calls) = msg.tool_calls {
|
if let Some(ref calls) = msg.tool_calls {
|
||||||
for call in calls {
|
for call in calls {
|
||||||
let _ = ui_tx.send(UiMessage::ToolCall {
|
let _ = ui_tx.send(UiMessage::ToolCall {
|
||||||
|
|
@ -1156,7 +1158,7 @@ fn replay_session_to_ui(entries: &[types::ConversationEntry], ui_tx: &ui_channel
|
||||||
.send(UiMessage::TextDelta(format!("{}\n", text), target));
|
.send(UiMessage::TextDelta(format!("{}\n", text), target));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
types::Role::Tool => {
|
api_types::Role::Tool => {
|
||||||
let text = msg.content_text();
|
let text = msg.content_text();
|
||||||
let preview: String =
|
let preview: String =
|
||||||
text.lines().take(3).collect::<Vec<_>>().join("\n");
|
text.lines().take(3).collect::<Vec<_>>().join("\n");
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@
|
||||||
//
|
//
|
||||||
// Activated when config has api_base_url set.
|
// Activated when config has api_base_url set.
|
||||||
|
|
||||||
use crate::user::api::ApiClient;
|
use crate::agent::api::ApiClient;
|
||||||
use crate::user::types::*;
|
use crate::agent::api::types::*;
|
||||||
use crate::agent::tools::{self as agent_tools};
|
use crate::agent::tools::{self as agent_tools, ToolOutput};
|
||||||
|
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ use std::fs::{File, OpenOptions};
|
||||||
use std::io::{BufRead, BufReader, Seek, SeekFrom, Write};
|
use std::io::{BufRead, BufReader, Seek, SeekFrom, Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use crate::user::types::ConversationEntry;
|
use crate::agent::context::ConversationEntry;
|
||||||
|
|
||||||
pub struct ConversationLog {
|
pub struct ConversationLog {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,6 @@
|
||||||
// - cli, context, dmn, identity, log, observe, parsing, tui
|
// - cli, context, dmn, identity, log, observe, parsing, tui
|
||||||
// Config moved to crate::config (unified with memory config)
|
// Config moved to crate::config (unified with memory config)
|
||||||
|
|
||||||
pub mod api;
|
|
||||||
pub mod types;
|
|
||||||
pub mod ui_channel;
|
pub mod ui_channel;
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
pub mod dmn;
|
pub mod dmn;
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@
|
||||||
// Also handles streaming artifacts: whitespace inside XML tags from
|
// Also handles streaming artifacts: whitespace inside XML tags from
|
||||||
// token boundaries, </think> tags, etc.
|
// token boundaries, </think> tags, etc.
|
||||||
|
|
||||||
use crate::user::types::*;
|
use crate::agent::api::types::*;
|
||||||
|
use crate::agent::tools::{ToolCall, ToolDef, FunctionCall};
|
||||||
|
|
||||||
/// Parse leaked tool calls from response text.
|
/// Parse leaked tool calls from response text.
|
||||||
/// Looks for `<tool_call>...</tool_call>` blocks and tries both
|
/// Looks for `<tool_call>...</tool_call>` blocks and tries both
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,11 @@
|
||||||
// subconscious_screen.rs — F3 subconscious (consolidation agents)
|
// subconscious_screen.rs — F3 subconscious (consolidation agents)
|
||||||
// unconscious_screen.rs — F4 unconscious (memory daemon status)
|
// unconscious_screen.rs — F4 unconscious (memory daemon status)
|
||||||
|
|
||||||
mod main_screen;
|
mod main;
|
||||||
mod context_screen;
|
mod context;
|
||||||
mod subconscious_screen;
|
mod subconscious;
|
||||||
mod unconscious_screen;
|
mod unconscious;
|
||||||
mod thalamus_screen;
|
mod thalamus;
|
||||||
|
|
||||||
pub(crate) const SCREEN_LEGEND: &str = " F1=interact F2=conscious F3=subconscious F4=unconscious F5=thalamus ";
|
pub(crate) const SCREEN_LEGEND: &str = " F1=interact F2=conscious F3=subconscious F4=unconscious F5=thalamus ";
|
||||||
/// Subconscious agents — interact with conscious context
|
/// Subconscious agents — interact with conscious context
|
||||||
|
|
|
||||||
|
|
@ -11,16 +11,11 @@
|
||||||
// The channel also fans out to a broadcast channel so the observation
|
// The channel also fans out to a broadcast channel so the observation
|
||||||
// socket (observe.rs) can subscribe without touching the main path.
|
// socket (observe.rs) can subscribe without touching the main path.
|
||||||
|
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::Arc;
|
||||||
use tokio::sync::{broadcast, mpsc};
|
use tokio::sync::{broadcast, mpsc};
|
||||||
|
|
||||||
/// Shared, live context state — agent writes, TUI reads for the debug screen.
|
// Re-export context types that moved to agent::context
|
||||||
pub type SharedContextState = Arc<RwLock<Vec<ContextSection>>>;
|
pub use crate::agent::context::{ContextSection, SharedContextState, shared_context_state};
|
||||||
|
|
||||||
/// Create a new shared context state.
|
|
||||||
pub fn shared_context_state() -> SharedContextState {
|
|
||||||
Arc::new(RwLock::new(Vec::new()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ActiveToolCall lives in agent::tools — re-export for TUI access
|
// ActiveToolCall lives in agent::tools — re-export for TUI access
|
||||||
pub use crate::agent::tools::ActiveToolCall;
|
pub use crate::agent::tools::ActiveToolCall;
|
||||||
|
|
@ -57,15 +52,6 @@ pub struct StatusInfo {
|
||||||
pub context_budget: String,
|
pub context_budget: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A section of the context window, possibly with children.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ContextSection {
|
|
||||||
pub name: String,
|
|
||||||
pub tokens: usize,
|
|
||||||
pub content: String,
|
|
||||||
pub children: Vec<ContextSection>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Context loading details for the debug screen.
|
/// Context loading details for the debug screen.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ContextInfo {
|
pub struct ContextInfo {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue