docs: tambah doc comment, logging, dan inline comments di semua 255 file

Meliputi:
- File-level //! doc comment: tujuan file, alur kerja, komponen utama
- Function-level /// doc comment: apa, parameter, return, flow, edge cases
- Struct/enum/trait /// doc comment: peran, field docs
- Tracing logging (tracing::info!/debug!/trace!/warn!/error!) di setiap fungsi
- Inline comments untuk variable dan branching logic penting
- Seluruh 8 crates di workspace: zesdex-backend, zesdex-cms, zesdex-entities,
  zesdex-iam, zesdex-infra, zesdex-ipc, zesdex-middleware, zesdex-utils
- Build: 0 errors, 242/242 tests passed
This commit is contained in:
asepharyana
2026-07-19 17:05:47 +07:00
parent 6680795ce7
commit 5aaedbf787
255 changed files with 5666 additions and 743 deletions
@@ -1,17 +1,39 @@
//! In-memory conversation state: message history plus the system prompt and
//! model parameters used to drive the LLM.
//!
//! # Flow
//!
//! [`Conversation::new`] → [`push`](Conversation::push) to add messages →
//! [`to_api_messages`](Conversation::to_api_messages) to format for the LLM
//! API (system prompt prepended). Persisted via [`save_conversation`](Conversation::save_conversation)
//! and loaded via [`load_conversation`](Conversation::load_conversation).
//! The system prompt can be hot-swapped via [`rebuild_system`](Conversation::rebuild_system).
//!
//! # Components
//!
//! - `Conversation` — message vector + session metadata + generation params
//! - `push` / `rebuild_system` — mutation helpers
//! - `to_api_messages` — formats messages for API consumption
//! - `save_conversation` / `load_conversation` — filesystem persistence
use serde::{Deserialize, Serialize};
use tracing;
use super::message::{ChatMessage, Role};
/// A single conversation's message history and generation settings.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Conversation {
/// Ordered list of chat messages (user, assistant, tool, system).
pub messages: Vec<ChatMessage>,
/// System prompt prepended at request time (see `to_api_messages`).
pub system_prompt: String,
/// Foreign key referencing the owning session.
pub session_id: String,
/// Model identifier string, e.g. `"anthropic/claude-opus-4-8"`.
pub model: String,
/// Optional cap on output tokens.
pub max_tokens: Option<u32>,
/// Optional temperature (0.0 2.0).
pub temperature: Option<f32>,
}
@@ -78,6 +100,7 @@ impl Conversation {
let dir = base_dir.join("sessions").join(&self.session_id);
std::fs::create_dir_all(&dir)?;
let path = dir.join("conversation.json");
tracing::debug!(session_id = %self.session_id, path = %path.display(), "saving conversation");
zesdex_utils::write_json_atomic(&path, self, None)?;
Ok(())
}
@@ -97,7 +120,8 @@ impl Conversation {
.join("sessions")
.join(session_id)
.join("conversation.json");
let data = std::fs::read_to_string(path)?;
tracing::debug!(session_id = %session_id, path = %path.display(), "loading conversation");
let data = std::fs::read_to_string(&path)?;
let conv: Conversation = serde_json::from_str(&data)?;
Ok(conv)
}
@@ -1,8 +1,20 @@
//! Chat message types shared across the entity layer: `Role` and `ChatMessage`
//! with convenience constructors.
//! Chat message types shared across the entity layer.
//!
//! Provides [`Role`] (conversation participant) and [`ChatMessage`] (a single
//! message with optional tool-call metadata). Includes convenience constructors
//! for each role: `user`, `assistant`, `system`, `tool`/`tool_result`.
//!
//! # Flow
//!
//! Messages are constructed via the typed constructors → pushed into
//! [`Conversation`](super::conversation::Conversation) → serialized as JSON
//! to `conversation.json`.
use serde::{Deserialize, Serialize};
/// The conversation participant who authored a message.
///
/// Variants: `User`, `Assistant`, `System`, `Tool`. Serialized as lowercase
/// strings (e.g. `"user"`, `"assistant"`, `"system"`, `"tool"`).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Role {
#[serde(rename = "user")]
@@ -37,12 +49,18 @@ impl std::fmt::Display for Role {
/// chat-completion API structures.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatMessage {
/// Who sent this message (user, assistant, system, tool).
pub role: Role,
/// The message text content. `None` for assistant messages that only
/// contain tool calls.
pub content: Option<String>,
/// Tool-call requests attached to an assistant message (OpenAI-style).
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_calls: Option<Vec<super::tool_call::ToolCall>>,
/// For tool-role messages: the `id` of the `ToolCall` being responded to.
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_call_id: Option<String>,
/// Optional function name for the tool invocation.
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
@@ -1,6 +1,20 @@
//! Common entity types shared across the Zesdex application: application
//! configuration, settings, store paths, conversations, messages, tool
//! calls, usage stats, and SSE streaming types.
//! Common entity types shared across the Zesdex application.
//!
//! Contains pure data structures for conversations, messages, tool calls,
//! usage statistics, provider API types (chat request/response, SSE stream),
//! and store path configuration. All types derive `Serialize`/`Deserialize`
//! and are persisted as JSON files.
//!
//! # Sub-modules
//!
//! - [`conversation`] — Ordered conversation (vector of `ChatMessage`)
//! - [`message`] — `ChatMessage` + `Role` enum
//! - [`provider`] — LLM provider API types: `ChatRequest`, `ChatResponse`,
//! `StreamEvent`, `SseParser`, `ToolDef`, etc.
//! - [`store`] — `Store` paths for data directories
//! - [`tool_call`] — `ToolCall` + `ToolFunction` (function-calling request)
//! - [`tool_result`] — `ToolCallResult` (function-calling response)
//! - [`usage`] — `UsageStats` (token counts, costs)
pub mod conversation;
pub mod message;
@@ -1,7 +1,25 @@
//! Provider-facing DTOs: chat completion request, response, streaming types,
//! and the SSE stream parser.
//!
//! # Flow
//!
//! 1. **Request** — [`ChatRequest`] is built with model, messages, tools,
//! streaming options and sent to the LLM provider.
//! 2. **Response** — Non-streaming responses arrive as [`ChatResponse`] with
//! [`Choice`]s containing the full [`ChatMessage`](super::message::ChatMessage).
//! 3. **Streaming** — SSE chunks are fed into [`SseParser::feed`] which yields
//! [`StreamEvent`]s: token/text, reasoning, tool-call deltas, usage, done.
//!
//! # Components
//!
//! - `ChatRequest` / `StreamOptions` / `ToolDef` / `ToolFunctionDef` — outbound
//! - `ChatResponse` / `Choice` / `Delta` / `TokenUsage` — non-streaming inbound
//! - `StreamEvent` — one atomic streaming event (Token, Reasoning,
//! ToolCallDelta, Usage, Done, Error)
//! - `SseParser` — incremental SSE frame parser: `feed()` → `Vec<StreamEvent>`
use serde::{Deserialize, Serialize};
use serde_json::Value;
use tracing;
// ---------------------------------------------------------------------------
// Chat request / response
@@ -11,23 +29,32 @@ use serde_json::Value;
/// provider.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatRequest {
/// Model identifier, e.g. `"anthropic/claude-opus-4-8"`.
pub model: String,
/// Full message history (system + user + assistant + tool turns).
pub messages: Vec<super::message::ChatMessage>,
/// Maximum number of output tokens.
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u32>,
/// Sampling temperature (0.0 2.0).
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
/// Tool definitions available to the model.
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<ToolDef>>,
/// Controls which (if any) function is called by the model.
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_choice: Option<Value>,
/// Whether to use SSE streaming (`true`) or a single response.
#[serde(skip_serializing_if = "Option::is_none")]
pub stream: Option<bool>,
/// Nucleus sampling threshold.
#[serde(skip_serializing_if = "Option::is_none")]
pub top_p: Option<f32>,
/// Sequences where the model should stop generation.
#[serde(skip_serializing_if = "Option::is_none")]
pub stop: Option<Vec<String>>,
/// Additional streaming options (e.g. `include_usage`).
#[serde(skip_serializing_if = "Option::is_none")]
pub stream_options: Option<StreamOptions>,
}
@@ -42,38 +69,53 @@ pub struct StreamOptions {
/// Wire format for a single tool definition sent to the provider.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolDef {
/// The tool type discriminator, e.g. `"function"`.
#[serde(rename = "type")]
pub type_: String,
/// The function definition (name, description, JSON schema).
pub function: ToolFunctionDef,
}
/// Name, description, and JSON schema parameters for a tool definition.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolFunctionDef {
/// The function name the model may invoke.
pub name: String,
/// Human-readable description of what the function does.
pub description: String,
/// JSON Schema object describing the expected arguments.
pub parameters: Value,
}
/// Non-streaming chat completion response returned by the provider.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatResponse {
/// Unique response identifier from the provider.
pub id: String,
/// Object type, e.g. `"chat.completion"`.
pub object: Option<String>,
/// Model identifier that produced this response.
pub model: String,
/// One or more completion candidates.
pub choices: Vec<Choice>,
/// Token usage statistics (prompt, completion, total).
pub usage: Option<TokenUsage>,
/// Unix-timestamp of response creation.
pub created: Option<i64>,
}
/// One completion candidate within a `ChatResponse.choices` list.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Choice {
/// Zero-based index of this choice in the candidate list.
pub index: u32,
/// Full message (non-streaming response).
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<super::message::ChatMessage>,
/// Incremental delta (streaming response).
#[serde(skip_serializing_if = "Option::is_none")]
pub delta: Option<Delta>,
/// Why the model stopped: `"stop"`, `"tool_calls"`, `"length"`, etc.
#[serde(skip_serializing_if = "Option::is_none")]
pub finish_reason: Option<String>,
}
@@ -81,10 +123,13 @@ pub struct Choice {
/// Incremental delta emitted in a streaming SSE chunk.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Delta {
/// Role being set for the first streaming chunk.
#[serde(skip_serializing_if = "Option::is_none")]
pub role: Option<super::message::Role>,
/// Incremental text content delta.
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
/// Incremental tool-call delta (partial name/arguments).
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_calls: Option<Vec<super::tool_call::ToolCall>>,
}
@@ -92,11 +137,16 @@ pub struct Delta {
/// Token counts and optional cost breakdown for a single completion request.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct TokenUsage {
/// Tokens consumed by the prompt (input).
pub prompt_tokens: u32,
/// Tokens consumed by the completion (output).
pub completion_tokens: u32,
/// Sum of prompt + completion tokens.
pub total_tokens: u32,
/// Estimated cost for prompt tokens (provider-specific).
#[serde(skip_serializing_if = "Option::is_none")]
pub prompt_tokens_cost: Option<f64>,
/// Estimated cost for completion tokens (provider-specific).
#[serde(skip_serializing_if = "Option::is_none")]
pub completion_tokens_cost: Option<f64>,
}
@@ -108,28 +158,41 @@ pub struct TokenUsage {
/// One atomic event extracted from an LLM streaming response stream.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum StreamEvent {
/// An incremental text token.
Token(String),
/// An incremental reasoning token (Anthropic `reasoning_content`).
Reasoning(String),
/// An incremental tool-call delta (partial ID, name, or arguments).
ToolCallDelta {
/// Tool-call index (multiple calls in one response).
index: usize,
/// Optional tool-call ID (usually in the first delta for a call).
id: Option<String>,
/// Optional function name (usually in the first delta for a call).
name: Option<String>,
/// Partial JSON arguments delta for this tool call.
arguments_delta: String,
},
/// Final usage chunk with token counts.
Usage {
prompt_tokens: u64,
completion_tokens: u64,
total_tokens: u64,
},
/// Stream complete (all tokens have been delivered).
Done,
/// A stream-level error occurred.
Error(String),
}
/// Buffered SSE frame parser that accumulates raw `data:` lines and
/// flushes a `StreamEvent` on each blank-line boundary.
pub struct SseParser {
/// Leftover bytes from the last chunk that did not end with `\n`.
buffer: String,
/// The current `event:` type (set by `event:` lines, cleared on flush).
event_type: Option<String>,
/// Accumulated `data:` lines for the current event frame.
data_lines: Vec<String>,
}
@@ -1,6 +1,18 @@
//! Filesystem layout for zesdex's persistent and scratch data directories.
//!
//! # Flow
//!
//! [`Store::new`] resolves all paths from OS data dir / temp dir →
//! [`ensure_dirs`](Store::ensure_dirs) creates them on startup.
//!
//! # Components
//!
//! - `Store` — resolved path bundle (base, memory, scratch, images, downloads)
//! - `new` — path computation (no I/O)
//! - `ensure_dirs` — creates all directories if missing
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use tracing;
/// Resolved paths for all data directories zesdex reads from and writes to.
///
@@ -8,10 +20,15 @@ use std::path::PathBuf;
/// where memory, scratch, session images, and downloads live.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Store {
/// Top-level data directory, e.g. `~/.local/share/zesdex`.
pub base_dir: PathBuf,
/// Temporary scratch root, usually under the OS temp dir.
pub scratch_root: PathBuf,
/// Directory for persistent memory files (`.md` summaries).
pub memory_dir: PathBuf,
/// Directory for per-session image snapshots.
pub session_images_dir: PathBuf,
/// Directory for downloaded files.
pub download_dir: PathBuf,
}
@@ -41,6 +58,7 @@ impl Store {
///
/// Return: `Err` on the first directory that fails to create.
pub fn ensure_dirs(&self) -> std::io::Result<()> {
tracing::debug!(base = %self.base_dir.display(), "ensuring store directories exist");
std::fs::create_dir_all(&self.base_dir)?;
std::fs::create_dir_all(&self.memory_dir)?;
std::fs::create_dir_all(&self.scratch_root)?;
@@ -1,25 +1,42 @@
//! Tool-call DTOs embedded in assistant chat messages.
//!
//! Flow: provider response/stream carries `tool_calls` on an assistant
//! message → deserialized into `ToolCall`/`ToolFunction` → harness resolves
//! `function.name` against `all_tools()` and runs it with
//! `sanitize_tool_arguments(function.arguments)`.
//! # Flow
//!
//! Provider response/stream carries `tool_calls` on an assistant message →
//! deserialized into [`ToolCall`]/[`ToolFunction`] → harness resolves the
//! function name against `all_tools()` and runs it after sanitizing arguments
//! via [`sanitize_tool_arguments`] (which handles string-encoded JSON,
//! control characters, and truncation).
//!
//! # Components
//!
//! - `ToolCall` — a single tool-invocation request (id + type + function)
//! - `ToolFunction` — function name + raw arguments Value
//! - `sanitize_tool_arguments` — normalizes argument shape, repairs truncation
//! - `repair_json` — closes unclosed strings/braces/brackets in truncated JSON
use serde::{Deserialize, Serialize};
use serde_json::Value;
use tracing;
/// A single tool-call request emitted by the model in an assistant message.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCall {
/// Unique identifier for this tool call (referenced by `ToolCallResult`).
pub id: String,
/// Discriminator, e.g. `"function"`.
#[serde(rename = "type")]
pub type_: String,
/// The function to invoke (name + arguments).
pub function: ToolFunction,
}
/// The function name and raw arguments payload for a `ToolCall`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolFunction {
/// The function/tool name to dispatch against.
pub name: String,
/// Arguments as a JSON Value (may be a string-encoded object before
/// `sanitize_tool_arguments` normalises it).
pub arguments: Value,
}
@@ -1,13 +1,29 @@
//! Record of one completed tool invocation, kept for transcript/history.
//!
//! # Flow
//!
//! Tool harness completes execution → creates [`ToolCallResult`] with output,
//! error flag, and wall-clock duration → appended to conversation history as
//! a `Tool`-role [`ChatMessage`](super::message::ChatMessage).
//!
//! # Components
//!
//! - `ToolCallResult` — tool name + output + error flag + duration
//! - `new` — convenience constructor
use serde::{Deserialize, Serialize};
/// Record of a completed tool invocation, kept for transcript/history.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCallResult {
/// The `id` of the `ToolCall` this result responds to.
pub tool_call_id: String,
/// The name of the tool that was invoked.
pub tool_name: String,
/// The text output produced by the tool (or error message).
pub output: String,
/// Whether the tool exited with an error.
pub is_error: bool,
/// Wall-clock execution duration in milliseconds.
pub duration_ms: u64,
}
@@ -1,17 +1,34 @@
//! Token usage accounting shared by streaming and non-streaming responses.
//!
//! # Flow
//!
//! Accumulated across all LLM API calls in a session. Each response updates
//! the running totals; `last_*` fields capture the most recent call's values
//! for interpolation display. Persisted alongside other session metadata.
//!
//! # Components
//!
//! - `UsageStats` — cumulative token/latency counters
use serde::{Deserialize, Serialize};
/// Cumulative token/latency counters for a session, persisted alongside it.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
pub struct UsageStats {
/// Total tokens consumed as input (prompt).
pub tokens_in: u64,
/// Total tokens generated as output (completion).
pub tokens_out: u64,
/// Most recent call's input tokens (for live interpolation display).
#[serde(default)]
pub last_tokens_in: u64,
/// Most recent call's output tokens (for live interpolation display).
#[serde(default)]
pub last_tokens_out: u64,
/// Total number of LLM API calls made this session.
pub api_calls: u64,
/// Tokens consumed by auto-review subagent calls.
pub review_tokens: u64,
/// Total wall-clock time spent on LLM API calls (milliseconds).
pub total_ms: u64,
}