blob: 703455b8909cef083ef71f9ac16c68058ca10fa4 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
//! Source-scoped filesystem operations. Host supplies authority, not callers.
pub mod database;
mod directory;
pub mod entries;
mod read;
use maka_agent_facts::read::ReadInput;
pub use read::{
FileInfo, ListInput, OpenFile, PinnedFile, PinnedReader, ReadAuthorization, ReadDirectory,
ReadError, ReadInput as ReadViewInput, ReadRange, ReadRoot, Reader, Symlinks,
};
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
/// Authenticated call source and current workspace permission are both checked
/// by Host. Supplying a Session ID is not a way to acquire this authority.
pub trait Files: Send + Sync {
fn invoke(
&self,
call: crate::call::Scope,
operation: Operation,
) -> futures_util::future::BoxFuture<'_, Result<Output, maka_agent_facts::tools::ToolError>>;
}
/// Native callers retain image bytes; only the JS boundary encodes them. Tool
/// calls may instead return their canonical artifact reference as ordinary data.
pub enum Output {
Value(Value),
Image { bytes: Vec<u8>, mime_type: String },
Entries(entries::Output),
}
impl Output {
pub fn into_json(self) -> Value {
match self {
Self::Value(value) => value,
Self::Entries(value) => {
serde_json::to_value(value).expect("filesystem result serialization")
}
Self::Image { bytes, mime_type } => {
json!({"kind":"image", "mimeType":mime_type, "bytes":bytes})
}
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(
tag = "kind",
content = "input",
rename_all = "snake_case",
deny_unknown_fields
)]
pub enum Operation {
Read(ReadInput),
Write(Write),
Edit(Edit),
Glob(Glob),
Grep(Grep),
Patch(Patch),
Entries(entries::Operation),
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Write {
pub path: String,
pub content: String,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Edit {
pub path: String,
pub old_string: String,
pub new_string: String,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Glob {
pub pattern: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cwd: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Grep {
pub pattern: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub glob: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
pub enum Patch {
CreateFile { path: String, diff: String },
UpdateFile { path: String, diff: String },
DeleteFile { path: String },
}
impl Operation {
pub fn is_read(&self) -> bool {
match self {
Self::Read(_) | Self::Glob(_) | Self::Grep(_) => true,
Self::Entries(operation) => operation.is_read(),
_ => false,
}
}
pub fn required_tool(&self) -> &'static str {
match self {
Self::Entries(operation) => operation.required_tool(),
_ => self.name(),
}
}
pub fn name(&self) -> &'static str {
match self {
Self::Read(_) => "Read",
Self::Write(_) => "Write",
Self::Edit(_) => "Edit",
Self::Glob(_) => "Glob",
Self::Grep(_) => "Grep",
Self::Patch(_) => "apply_patch",
Self::Entries(operation) => operation.name(),
}
}
/// Reuse the native tool contract; schema and file-effect semantics have one owner.
pub fn into_tool_input(self, operation_id: &str) -> Value {
match self {
Self::Read(input) => json!(input),
Self::Write(input) => json!(input),
Self::Edit(input) => json!(input),
Self::Glob(input) => json!(input),
Self::Grep(input) => json!(input),
Self::Patch(operation) => json!({"callId":operation_id,"operation":operation}),
Self::Entries(operation) => json!(operation),
}
}
}