Roll back stored settings when a creation fails, and keep form ids unique
create_agent puts back the caps and inference settings it replaced if a later store or the job insert fails, so a refused creation leaves nothing behind for the name. The shared field components take an id prefix so the create dialog and an open editor never share a DOM id. The queue check now follows the hive roster check, the 422 is documented, and the copy says empty rather than closed and that destroy keeps stored settings.
This commit is contained in:
parent
e36a15de91
commit
37c91eb916
7 changed files with 288 additions and 47 deletions
|
|
@ -317,24 +317,29 @@ pub(crate) async fn put_agent_inference(
|
|||
Ok(Json(value.into()))
|
||||
}
|
||||
|
||||
/// What [`store`] replaced: the settings that were stored, or `None` for no
|
||||
/// key.
|
||||
pub(crate) struct Replaced(Option<AgentInference>);
|
||||
|
||||
/// Replace `agent`'s stored inference settings with `value`.
|
||||
pub(crate) async fn store(
|
||||
client: &async_nats::Client,
|
||||
agent: &str,
|
||||
value: &AgentInference,
|
||||
) -> Result<(), ProblemDetails> {
|
||||
) -> Result<Replaced, ProblemDetails> {
|
||||
let store = open(client).await?;
|
||||
for _ in 0..MAX_ATTEMPTS {
|
||||
let (current, revision, undecodable) = read_or_default::<AgentInference>(&store, agent)
|
||||
.await
|
||||
.map_err(|e| queue_problem(&e))?;
|
||||
let replaced = Replaced(revision.map(|_| current.clone()));
|
||||
if (current == *value && !undecodable)
|
||||
|| write(&store, agent, value, revision)
|
||||
.await
|
||||
.map_err(|e| queue_problem(&e))?
|
||||
{
|
||||
tracing::info!(%agent, ?value, "set inference settings");
|
||||
return Ok(());
|
||||
return Ok(replaced);
|
||||
}
|
||||
}
|
||||
Err(queue_problem(&anyhow::anyhow!(
|
||||
|
|
@ -343,6 +348,24 @@ pub(crate) async fn store(
|
|||
)))
|
||||
}
|
||||
|
||||
/// Put back what [`store`] replaced. Best effort: a failure is logged, since
|
||||
/// the caller is already on its way to an error of its own.
|
||||
pub(crate) async fn restore(client: &async_nats::Client, agent: &str, replaced: Replaced) {
|
||||
let put_back = match replaced.0 {
|
||||
Some(previous) => store(client, agent, &previous).await.map(|_| ()),
|
||||
None => match open(client).await {
|
||||
Ok(store) => store
|
||||
.delete(agent)
|
||||
.await
|
||||
.map_err(|e| queue_problem(&anyhow::Error::new(e))),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
};
|
||||
if let Err(e) = put_back {
|
||||
tracing::warn!(%agent, error = ?e, "could not put back the agent's inference settings");
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/api/agents/{name}/inference",
|
||||
|
|
@ -587,4 +610,32 @@ mod tests {
|
|||
let entry = store.get("atlas").await.expect("read").expect("a value");
|
||||
assert_eq!(&entry[..], b"{}");
|
||||
}
|
||||
|
||||
/// `restore` puts back the settings a `store` replaced, or takes the key
|
||||
/// out again when there was none.
|
||||
#[tokio::test]
|
||||
async fn restore_undoes_a_store() {
|
||||
let nats = crate::test_nats::TestNats::start();
|
||||
let client = nats.client().await;
|
||||
let bucket = agent_inference::open_or_create(&client)
|
||||
.await
|
||||
.expect("bucket");
|
||||
let value = |json: &str| settings(json).unwrap().into_stored().unwrap();
|
||||
|
||||
let first = store(&client, "atlas", &value(r#"{"model":"opus"}"#))
|
||||
.await
|
||||
.expect("stored");
|
||||
restore(&client, "atlas", first).await;
|
||||
assert!(bucket.get("atlas").await.expect("read").is_none());
|
||||
|
||||
store(&client, "atlas", &value(r#"{"model":"opus"}"#))
|
||||
.await
|
||||
.expect("stored");
|
||||
let second = store(&client, "atlas", &value(r#"{"model":"haiku"}"#))
|
||||
.await
|
||||
.expect("replaced");
|
||||
restore(&client, "atlas", second).await;
|
||||
let entry = bucket.get("atlas").await.expect("read").expect("a value");
|
||||
assert_eq!(&entry[..], br#"{"model":"opus"}"#);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue