swarm-controller: GET inference answers configured: null without a key
`GET /api/agents/{name}/inference` answered `"configured": {}` both for
an agent with no key in `agent-inference` and for a stored value with no
fields, so a reader could not tell "the swarm sets nothing" from "a
setting with every field unset". It now reads the key as
`Option<AgentInference>`: an absent or deleted key is `null`, a stored
`{}` stays `{}`. The shared `schedules::read` helper is unchanged.
`swarmctl agent inference get` prints "no swarm setting; the agent runs
on its own model and effort" for `null`; `set` merges into an empty
value in that case, as before.
The agent itself still treats no key and `{}` the same.
Refs #5034
This commit is contained in:
parent
47bd83054f
commit
7364ccdc98
3 changed files with 58 additions and 14 deletions
|
|
@ -106,8 +106,9 @@ impl InferenceSettings {
|
|||
/// What `GET` answers for one agent.
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct AgentInferenceView {
|
||||
/// The settings the swarm sets; `{}` when none is set.
|
||||
configured: InferenceSettings,
|
||||
/// The settings the swarm sets; `null` when it has none for the agent (no
|
||||
/// key, or a cleared one), `{}` for a stored setting with no fields.
|
||||
configured: Option<InferenceSettings>,
|
||||
/// What the agent reported running on in its latest turn-state header;
|
||||
/// `null` when this daemon has received none with these fields since it
|
||||
/// started.
|
||||
|
|
@ -241,11 +242,13 @@ pub(crate) async fn get_agent_inference(
|
|||
let agent = agent_name(&name).map_err(refused)?;
|
||||
let client = queue(&state).map_err(refused)?;
|
||||
let store = open(&client).await?;
|
||||
let (current, _) = read::<AgentInference>(&store, &agent)
|
||||
// `Option`'s default is the `None` an absent or deleted key reads as; a
|
||||
// stored `{}` decodes to `Some`.
|
||||
let (current, _) = read::<Option<AgentInference>>(&store, &agent)
|
||||
.await
|
||||
.map_err(|e| queue_problem(&e))?;
|
||||
Ok(Json(AgentInferenceView {
|
||||
configured: current.into(),
|
||||
configured: current.map(Into::into),
|
||||
effective: state
|
||||
.reported_inference
|
||||
.as_ref()
|
||||
|
|
@ -444,7 +447,7 @@ mod tests {
|
|||
fn the_view_answers_both_halves() {
|
||||
let view = |effective: Option<Effective>| {
|
||||
serde_json::to_value(AgentInferenceView {
|
||||
configured: InferenceSettings::default(),
|
||||
configured: Some(InferenceSettings::default()),
|
||||
effective: effective.map(Into::into),
|
||||
})
|
||||
.unwrap()
|
||||
|
|
@ -453,6 +456,14 @@ mod tests {
|
|||
view(None),
|
||||
serde_json::json!({"configured": {}, "effective": null})
|
||||
);
|
||||
let unset = AgentInferenceView {
|
||||
configured: None,
|
||||
effective: None,
|
||||
};
|
||||
assert_eq!(
|
||||
serde_json::to_value(unset).unwrap(),
|
||||
serde_json::json!({"configured": null, "effective": null})
|
||||
);
|
||||
let reported = Reported::default();
|
||||
reported.record(&iris(), NEW_HEADER);
|
||||
assert_eq!(
|
||||
|
|
@ -484,4 +495,15 @@ mod tests {
|
|||
let json = r#"{"model":"claude-opus-4-8","subagent_models":["claude-haiku-4-5"]}"#;
|
||||
assert!(settings(json).unwrap().into_stored().is_ok());
|
||||
}
|
||||
|
||||
/// What `get_agent_inference` reads with: an absent or deleted key is
|
||||
/// `Option`'s default, `None`; a stored `{}` is a setting with no fields.
|
||||
#[test]
|
||||
fn no_key_and_an_empty_setting_read_apart() {
|
||||
assert_eq!(Option::<AgentInference>::default(), None);
|
||||
assert_eq!(
|
||||
serde_json::from_slice::<Option<AgentInference>>(b"{}").unwrap(),
|
||||
Some(AgentInference::default())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue