Files
sausage/host/src/harness/credentials.rs
T

84 lines
3.2 KiB
Rust

//! Credential request snapshots and host-only provider configuration.
use crate::cred::{store::Cred, types::Subject};
use deno_core::{OpState, op2};
use deno_error::JsErrorBox;
use serde_json::{Value, json};
use std::{cell::RefCell, rc::Rc, sync::Arc};
pub(super) struct Notifications {
filed: tokio::sync::Mutex<tokio::sync::broadcast::Receiver<String>>,
}
impl Notifications {
pub(super) fn new(cred: &Cred) -> Rc<Self> {
Rc::new(Self {
filed: tokio::sync::Mutex::new(cred.subscribe_filed()),
})
}
}
#[op2]
pub(super) async fn op_credential_next(state: Rc<RefCell<OpState>>) {
let notifications = state.borrow().borrow::<Rc<Notifications>>().clone();
let mut filed = notifications.filed.lock().await;
let _ = tokio::time::timeout(std::time::Duration::from_secs(1), filed.recv()).await;
}
#[op2]
#[serde]
pub(super) fn op_credential_requests(state: &mut OpState) -> Result<serde_json::Value, JsErrorBox> {
let cred = state.borrow::<Arc<Cred>>();
let work = || -> anyhow::Result<Value> {
cred.expire_requests()?;
// TODO(T27): prune retained records once a day after the library adds Cred::prune.
let mut rows = Vec::new();
for request in cred.request_list()? {
let Subject::Conversation(conversation) = request.subject else {
continue;
};
let names = request
.payload
.get("items")
.and_then(Value::as_array)
.map(|items| {
items
.iter()
.filter_map(Value::as_str)
.map(|id| {
cred.item_get(id)
.map(|item| format!("`{}`", item.name))
.unwrap_or_else(|_| "the credential".into())
})
.collect::<Vec<_>>()
.join(", ")
})
.unwrap_or_else(|| "the credential change".into());
let message = match request.state.as_str() {
"approved" => format!("Your request for {names} was approved. Retry the command."),
"denied" => format!("Your request for {names} was denied."),
"expired" => format!(
"Your request for {names} expired. File a new request if you still need it."
),
_ => String::new(),
};
rows.push(json!({"id":request.id,"conversation":conversation,"state":request.state,"message":message}));
}
Ok(json!(rows))
};
work().map_err(|_| JsErrorBox::generic("Cannot read credential requests"))
}
#[op2]
#[serde]
pub(super) async fn op_model_providers(
state: Rc<RefCell<OpState>>,
) -> Result<serde_json::Value, JsErrorBox> {
let root = state
.borrow()
.borrow::<super::environment::Environment>()
.root
.clone();
let config = super::config::Config::read(&root)
.await
.map_err(|error| JsErrorBox::generic(error.to_string()))?;
serde_json::to_value(config.providers).map_err(|error| JsErrorBox::generic(error.to_string()))
}