mobius 0.9.5

A small, modular Rust framework for building coding agents
Documentation
use serde::Deserialize;
use serde_json::Value;

use super::presentation::publish_current_widgets;
use super::{MAX_NOTE_BYTES, ScratchpadStore, WriteOutcome, text};
use crate::backend::model::ToolDefinition;
use crate::middleware::FrontendEventSink;
use crate::middleware::tools::{ApprovalRequirement, Tool, ToolContext};
use crate::{BoxFuture, Result};

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct NoteArgs {
    note: String,
}

pub(super) struct WriteScratchpad {
    pub(super) store: ScratchpadStore,
    pub(super) session_id: String,
    pub(super) frontend: FrontendEventSink,
}

impl Tool for WriteScratchpad {
    fn definition(&self) -> ToolDefinition {
        ToolDefinition {
            name: "write_scratchpad".into(),
            description: text::TOOL_WRITE_SCRATCHPAD_DESCRIPTION.into(),
            parameters: note_schema(text::TOOL_WRITE_SCRATCHPAD_PARAMETER_NOTE_DESCRIPTION),
        }
    }

    fn call<'a>(
        &'a self,
        _context: ToolContext,
        arguments: Value,
    ) -> BoxFuture<'a, Result<String>> {
        Box::pin(async move {
            let arguments: NoteArgs = serde_json::from_value(arguments)?;
            let outcome = self
                .store
                .write_session(&self.session_id, &arguments.note)
                .await?;
            if outcome != WriteOutcome::Existing {
                publish_current_widgets(&self.store, &self.session_id, &self.frontend).await?;
            }
            Ok(match outcome {
                WriteOutcome::Added => text::MESSAGE_ADDED_SESSION.into(),
                WriteOutcome::Updated => text::MESSAGE_UPDATED_SESSION.into(),
                WriteOutcome::Existing => text::MESSAGE_EXISTING_SESSION.into(),
            })
        })
    }
}

pub(super) struct PromoteScratchpad {
    pub(super) store: ScratchpadStore,
    pub(super) session_id: String,
    pub(super) frontend: FrontendEventSink,
}

impl Tool for PromoteScratchpad {
    fn definition(&self) -> ToolDefinition {
        ToolDefinition {
            name: "promote_scratchpad".into(),
            description: text::TOOL_PROMOTE_SCRATCHPAD_DESCRIPTION.into(),
            parameters: note_schema(text::TOOL_PROMOTE_SCRATCHPAD_PARAMETER_NOTE_DESCRIPTION),
        }
    }

    fn approval(&self) -> ApprovalRequirement {
        ApprovalRequirement::Always
    }

    fn call<'a>(
        &'a self,
        _context: ToolContext,
        arguments: Value,
    ) -> BoxFuture<'a, Result<String>> {
        Box::pin(async move {
            let arguments: NoteArgs = serde_json::from_value(arguments)?;
            let outcome = self
                .store
                .promote_note(&self.session_id, &arguments.note)
                .await?;
            if outcome != WriteOutcome::Existing {
                publish_current_widgets(&self.store, &self.session_id, &self.frontend).await?;
            }
            Ok(match outcome {
                WriteOutcome::Added => text::MESSAGE_PROMOTED_GLOBAL.into(),
                WriteOutcome::Updated => text::MESSAGE_UPGRADED_GLOBAL.into(),
                WriteOutcome::Existing => text::MESSAGE_EXISTING_GLOBAL.into(),
            })
        })
    }
}

fn note_schema(description: &str) -> Value {
    serde_json::json!({
        "type": "object",
        "properties": {
            "note": {
                "type": "string",
                "description": description,
                "maxLength": MAX_NOTE_BYTES
            }
        },
        "required": ["note"],
        "additionalProperties": false
    })
}