mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
- source.rs: Add ClawSource adapter that reads .claw/sessions/*.jsonl (skips
LFS stubs), parses both full-JSON and JSONL-stream formats, normalises into
ChatSession/ChatMessage identical to the OpenCode path.
- models.rs: Add title/agent/model fields to ChatSession; remove unused
chrono import.
- normalize.rs: Populate title/agent/model from OpenCodeSession.
- sink.rs: Full rewrite:
- DDL adds title/agent/model columns + ALTER TABLE IF NOT EXISTS migration
path for existing clusters.
- upsert_session and upsert_messages parameter counts match placeholders
($19 and $13 respectively).
- FTS index uses COALESCE to handle NULL text_content.
- sslmode= token stripped before parse so NoTls connect doesn't reject
it; README documents the SSL upgrade path (tokio-postgres-rustls).
- with-serde_json-1 feature added to tokio-postgres dep for JSONB params.
- main.rs:
- Replace DefaultHasher sha256_text with FNV-1a 64-bit (correct hex digest).
- Add ClawSync, List, Get subcommands.
- Remove unused Context import.
- bridge.rs: Rewrite to use bridge_wrapper.py subprocess rather than inline
-c script; resolves wrapper path relative to infra_dir or CARGO_MANIFEST_DIR;
unwraps {"ok":true,"data":{}} envelope; health_check() tests python3 only.
- embed.rs: Remove unused OllamaEmbedRequest import.
- systemd/: Add ene-session-sync.service + .timer (5-min cadence, 2-min
OnBootSec, Persistent=true for catch-up after suspend).
- README.md: Architecture diagram, CLI reference, schema, build/install,
systemd install, Python bridge setup.
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
256 lines
6.9 KiB
Rust
256 lines
6.9 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
|
|
/// Raw session row from opencode.db.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct OpenCodeSession {
|
|
pub id: String,
|
|
pub project_id: String,
|
|
pub parent_id: Option<String>,
|
|
pub slug: String,
|
|
pub directory: String,
|
|
pub title: String,
|
|
pub version: String,
|
|
pub share_url: Option<String>,
|
|
pub summary_additions: Option<i64>,
|
|
pub summary_deletions: Option<i64>,
|
|
pub summary_files: Option<i64>,
|
|
pub summary_diffs: Option<String>,
|
|
pub revert: Option<String>,
|
|
pub permission: Option<String>,
|
|
pub time_created: i64,
|
|
pub time_updated: i64,
|
|
pub time_compacting: Option<i64>,
|
|
pub time_archived: Option<i64>,
|
|
pub workspace_id: Option<String>,
|
|
pub path: Option<String>,
|
|
pub agent: Option<String>,
|
|
pub model: Option<String>,
|
|
pub cost: f64,
|
|
pub tokens_input: i64,
|
|
pub tokens_output: i64,
|
|
pub tokens_reasoning: i64,
|
|
pub tokens_cache_read: i64,
|
|
pub tokens_cache_write: i64,
|
|
}
|
|
|
|
/// Raw message row from opencode.db.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct OpenCodeMessage {
|
|
pub id: String,
|
|
pub session_id: String,
|
|
pub time_created: i64,
|
|
pub time_updated: i64,
|
|
pub data: MessageData,
|
|
}
|
|
|
|
/// The JSON blob inside message.data.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MessageData {
|
|
pub role: String,
|
|
#[serde(default)]
|
|
pub time: Option<MessageTime>,
|
|
#[serde(default)]
|
|
pub tokens: Option<MessageTokens>,
|
|
#[serde(default)]
|
|
pub cost: Option<f64>,
|
|
#[serde(default)]
|
|
pub model_id: Option<String>,
|
|
#[serde(default)]
|
|
pub provider_id: Option<String>,
|
|
#[serde(default)]
|
|
pub agent: Option<String>,
|
|
#[serde(default)]
|
|
pub mode: Option<String>,
|
|
#[serde(default)]
|
|
pub path: Option<String>,
|
|
#[serde(default)]
|
|
pub finish: Option<String>,
|
|
#[serde(default)]
|
|
pub summary: Option<MessageSummary>,
|
|
#[serde(default)]
|
|
pub parent_id: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MessageTime {
|
|
pub created: Option<i64>,
|
|
pub completed: Option<i64>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MessageTokens {
|
|
pub input: Option<i64>,
|
|
pub output: Option<i64>,
|
|
pub reasoning: Option<i64>,
|
|
pub cache: Option<MessageCache>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MessageCache {
|
|
pub creation: Option<i64>,
|
|
pub read: Option<i64>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MessageSummary {
|
|
#[serde(default)]
|
|
pub diffs: Option<Vec<String>>,
|
|
}
|
|
|
|
/// Raw part row from opencode.db.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct OpenCodePart {
|
|
pub id: String,
|
|
pub message_id: String,
|
|
pub session_id: String,
|
|
pub time_created: i64,
|
|
pub time_updated: i64,
|
|
pub data: PartData,
|
|
}
|
|
|
|
/// The JSON blob inside part.data.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct PartData {
|
|
pub r#type: String,
|
|
#[serde(default)]
|
|
pub text: Option<String>,
|
|
#[serde(default)]
|
|
pub tool: Option<String>,
|
|
#[serde(default)]
|
|
pub call_id: Option<String>,
|
|
#[serde(default, rename = "callID")]
|
|
pub call_id_alt: Option<String>,
|
|
#[serde(default)]
|
|
pub state: Option<String>,
|
|
#[serde(default)]
|
|
pub input: Option<serde_json::Value>,
|
|
#[serde(default)]
|
|
pub output: Option<serde_json::Value>,
|
|
#[serde(default)]
|
|
pub is_error: Option<bool>,
|
|
}
|
|
|
|
impl PartData {
|
|
pub fn call_id(&self) -> Option<String> {
|
|
self.call_id.clone().or_else(|| self.call_id_alt.clone())
|
|
}
|
|
}
|
|
|
|
/// Raw session_message row from opencode.db.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct OpenCodeSessionMessage {
|
|
pub id: String,
|
|
pub session_id: String,
|
|
pub r#type: String,
|
|
pub time_created: i64,
|
|
pub time_updated: i64,
|
|
pub data: serde_json::Value,
|
|
}
|
|
|
|
/// Normalized chat session ready for RDS.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ChatSession {
|
|
pub session_id: String,
|
|
pub title: Option<String>,
|
|
pub agent: Option<String>,
|
|
pub model: Option<String>,
|
|
pub workspace_fingerprint: Option<String>,
|
|
pub workspace_root: Option<String>,
|
|
pub fork_parent_session_id: Option<String>,
|
|
pub compaction_count: i32,
|
|
pub compaction_summary: Option<String>,
|
|
pub message_count: i32,
|
|
pub token_input_total: i64,
|
|
pub token_output_total: i64,
|
|
pub created_at_ms: i64,
|
|
pub updated_at_ms: i64,
|
|
pub first_message_at_ms: Option<i64>,
|
|
pub last_message_at_ms: Option<i64>,
|
|
pub meta: serde_json::Value,
|
|
pub embedding: Option<Vec<f32>>,
|
|
pub receipt: Option<String>,
|
|
}
|
|
|
|
/// Normalized chat message ready for RDS.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ChatMessage {
|
|
pub session_id: String,
|
|
pub message_index: i32,
|
|
pub role: String,
|
|
pub blocks: Vec<MessageBlock>,
|
|
pub text_content: String,
|
|
pub token_input: i64,
|
|
pub token_output: i64,
|
|
pub token_cache_creation: i64,
|
|
pub token_cache_read: i64,
|
|
pub tool_calls: Vec<ToolCall>,
|
|
pub embedding: Option<Vec<f32>>,
|
|
pub receipt_hash: Option<String>,
|
|
pub created_at_ms: i64,
|
|
}
|
|
|
|
/// A content block within a message (text, reasoning, tool-use, tool-result).
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MessageBlock {
|
|
pub block_type: String,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub text: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub tool_name: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub tool_input: Option<serde_json::Value>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub tool_output: Option<serde_json::Value>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub is_error: Option<bool>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ToolCall {
|
|
pub call_id: String,
|
|
pub tool_name: String,
|
|
pub input: serde_json::Value,
|
|
}
|
|
|
|
/// Bridge request to a Python surface.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct BridgeRequest {
|
|
pub module: String,
|
|
pub operation: String,
|
|
#[serde(default)]
|
|
pub payload: serde_json::Value,
|
|
}
|
|
|
|
/// Bridge response from a Python surface.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct BridgeResponse {
|
|
pub ok: bool,
|
|
#[serde(default)]
|
|
pub data: serde_json::Value,
|
|
#[serde(default)]
|
|
pub error: Option<String>,
|
|
}
|
|
|
|
/// Ollama embedding request.
|
|
#[derive(Debug, Clone, Serialize)]
|
|
pub struct OllamaEmbedRequest {
|
|
pub model: String,
|
|
pub prompt: String,
|
|
}
|
|
|
|
/// Ollama embedding response.
|
|
#[derive(Debug, Clone, Deserialize)]
|
|
pub struct OllamaEmbedResponse {
|
|
pub embedding: Vec<f32>,
|
|
}
|
|
|
|
/// Ingestion receipt written to ene.ingestion_receipts.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct IngestionReceipt {
|
|
pub shim_name: String,
|
|
pub status: String,
|
|
pub sha256: String,
|
|
pub record_count: i64,
|
|
pub source_path: String,
|
|
pub meta: serde_json::Value,
|
|
}
|