diff --git a/src/dispatch.rs b/src/dispatch.rs new file mode 100644 index 0000000..512558c --- /dev/null +++ b/src/dispatch.rs @@ -0,0 +1,77 @@ +//! Text-level codec dispatch: parse a harness's native session text into the +//! canonical model, or render the canonical model back to native text, keyed +//! by a runtime [`HarnessId`]. +//! +//! No filesystem, no `SQLite`, no subprocess: this is the codec layer alone +//! (see [`TextCodec`]), so it is available on every target, WASM included. The +//! WASM bindings and [`fidelity`](crate::fidelity) both route through it. + +use crate::harness::{ + amp, antigravity, campfire, chatgpt, claude_chat, claude_code, codex, cowork, cowork_remote, + cursor, cursor_desktop, fx, grok, grok_bot, hermes, opencode, pi, simple, +}; +use crate::transcript::{Codec, Common, HarnessId, TextCodec, Transcript}; + +/// Parse `harness`'s native session text into the canonical model. +/// +/// # Errors +/// When the text is not that harness's session format. +pub fn parse_to_common(harness: HarnessId, text: &str) -> crate::Result> { + fn go(text: &str) -> crate::Result> { + H::to_common(&H::from_text(text)?) + } + match harness { + HarnessId::ClaudeCode => go::(text), + HarnessId::ClaudeChat => go::(text), + HarnessId::CoworkRemote => go::(text), + HarnessId::ChatGpt => go::(text), + HarnessId::Codex => go::(text), + HarnessId::OpenCode => go::(text), + HarnessId::Pi => go::(text), + HarnessId::Campfire => go::(text), + HarnessId::Cursor => go::(text), + HarnessId::CursorDesktop => go::(text), + HarnessId::Grok => go::(text), + HarnessId::GrokBot => go::(text), + HarnessId::Fx => go::(text), + HarnessId::Hermes => go::(text), + HarnessId::Amp => go::(text), + HarnessId::Antigravity => go::(text), + HarnessId::Simple => go::(text), + HarnessId::Cowork => go::(text), + } +} + +/// Render the canonical model as `harness`'s native session text. +/// +/// # Errors +/// When the harness cannot represent the transcript (the live read-only +/// sources, for one). +pub fn render_from_common( + harness: HarnessId, + common: &Transcript, +) -> crate::Result { + fn go(common: &Transcript) -> crate::Result { + H::to_text(&H::from_common(common)?) + } + match harness { + HarnessId::ClaudeCode => go::(common), + HarnessId::ClaudeChat => go::(common), + HarnessId::CoworkRemote => go::(common), + HarnessId::ChatGpt => go::(common), + HarnessId::Codex => go::(common), + HarnessId::OpenCode => go::(common), + HarnessId::Pi => go::(common), + HarnessId::Campfire => go::(common), + HarnessId::Cursor => go::(common), + HarnessId::CursorDesktop => go::(common), + HarnessId::Grok => go::(common), + HarnessId::GrokBot => go::(common), + HarnessId::Fx => go::(common), + HarnessId::Hermes => go::(common), + HarnessId::Amp => go::(common), + HarnessId::Antigravity => go::(common), + HarnessId::Simple => go::(common), + HarnessId::Cowork => go::(common), + } +} diff --git a/src/lib.rs b/src/lib.rs index a340523..3dd58da 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,8 +17,11 @@ //! - [`Transcript`], [`Harness`], [`Codec`], [`Store`] — the generic type and //! the traits over it. //! - [`harness`] — one module per implemented harness. +//! - [`dispatch`] — parse or render a harness's native text, chosen by +//! [`HarnessId`] at run time. pub mod common; +pub mod dispatch; pub mod error; pub mod harness; #[cfg(not(target_arch = "wasm32"))] diff --git a/src/wasm.rs b/src/wasm.rs index e1bbb6f..2417b07 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -13,11 +13,8 @@ use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; use crate::common; -use crate::harness::{ - amp, antigravity, campfire, chatgpt, claude_chat, claude_code, codex, cowork, cowork_remote, - cursor, cursor_desktop, fx, grok, grok_bot, hermes, opencode, pi, simple, -}; -use crate::transcript::{Codec, Common, HarnessId, TextCodec, Transcript}; +use crate::dispatch::{parse_to_common, render_from_common}; +use crate::transcript::{HarnessId, Transcript}; /// Continue/convert a session from one harness's native text into another's. /// @@ -194,58 +191,6 @@ struct CommonJson { messages: Vec, } -fn parse_to_common(harness: HarnessId, text: &str) -> crate::Result> { - fn go(text: &str) -> crate::Result> { - H::to_common(&H::from_text(text)?) - } - match harness { - HarnessId::ClaudeCode => go::(text), - HarnessId::ClaudeChat => go::(text), - HarnessId::CoworkRemote => go::(text), - HarnessId::ChatGpt => go::(text), - HarnessId::Codex => go::(text), - HarnessId::OpenCode => go::(text), - HarnessId::Pi => go::(text), - HarnessId::Campfire => go::(text), - HarnessId::Cursor => go::(text), - HarnessId::CursorDesktop => go::(text), - HarnessId::Grok => go::(text), - HarnessId::GrokBot => go::(text), - HarnessId::Fx => go::(text), - HarnessId::Hermes => go::(text), - HarnessId::Amp => go::(text), - HarnessId::Antigravity => go::(text), - HarnessId::Simple => go::(text), - HarnessId::Cowork => go::(text), - } -} - -fn render_from_common(harness: HarnessId, common: &Transcript) -> crate::Result { - fn go(common: &Transcript) -> crate::Result { - H::to_text(&H::from_common(common)?) - } - match harness { - HarnessId::ClaudeCode => go::(common), - HarnessId::ClaudeChat => go::(common), - HarnessId::CoworkRemote => go::(common), - HarnessId::ChatGpt => go::(common), - HarnessId::Codex => go::(common), - HarnessId::OpenCode => go::(common), - HarnessId::Pi => go::(common), - HarnessId::Campfire => go::(common), - HarnessId::Cursor => go::(common), - HarnessId::CursorDesktop => go::(common), - HarnessId::Grok => go::(common), - HarnessId::GrokBot => go::(common), - HarnessId::Fx => go::(common), - HarnessId::Hermes => go::(common), - HarnessId::Amp => go::(common), - HarnessId::Antigravity => go::(common), - HarnessId::Simple => go::(common), - HarnessId::Cowork => go::(common), - } -} - fn parse_harness(name: &str) -> Result { name.parse().map_err(js) }