Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,20 @@ coval personas background-sounds upload ./lobby-noise.mp3 \
# Use the returned value, e.g. custom:bg123, on a persona
coval personas update <persona_id> --background custom:bg123

# Shape a persona's audio: placement, levels, and a hold-music cutoff
coval personas update <persona_id> \
--situate-speaker speakerphone-hard \
--voice-volume 1.4 \
--voice-speed 0.9 \
--background-sound-volume 0.3 \
--hold-music-timeout-seconds 45

# Channel degradation instead of placement (the two are mutually exclusive)
coval personas update <persona_id> --audio-degradation cell-handoff

# Clear a preset. A flag can only set a value, so clearing needs an explicit null.
coval personas update <persona_id> --input-json '{"situate_speaker":null}'

# Create a dashboard and make it the organization default
coval dashboards create \
--name "Production Metrics" \
Expand Down
34 changes: 12 additions & 22 deletions api-coverage-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ only when coverage actually changes.

| Metric | Value |
| --- | ---: |
| Reconciliation status | PASS |
| Reconciliation status | ACTION REQUIRED |
| Published operations | 181 |
| First-class CLI operations | 119 |
| Reviewed gaps | 62 |
| Client operations | 143 |
| Published request fields on covered operations | 360 |
| Request fields modeled by the CLI | 313 |
| Reviewed request-field gaps | 47 |
| Published request fields on covered operations | 363 |
| Request fields modeled by the CLI | 329 |
| Reviewed request-field gaps | 31 |

Catalog: https://api.coval.dev/v1/openapi

Expand Down Expand Up @@ -49,7 +49,8 @@ Catalog: https://api.coval.dev/v1/openapi

## Coverage snapshot mismatches

- None.
- `published_request_fields: recorded 360, current 363`
- `cli_modeled_request_fields: recorded 313, current 329`

## Client-only operations

Expand Down Expand Up @@ -122,7 +123,9 @@ Catalog: https://api.coval.dev/v1/openapi

## New published request fields the CLI drops

- None.
- `PATCH /metrics/{id} ivr_flow`
- `POST /metrics ivr_flow`
- `POST /runs config_overrides`

## Reviewed request-field gaps no longer present

Expand All @@ -144,14 +147,7 @@ Catalog: https://api.coval.dev/v1/openapi
- `PATCH /agents/{id} tags`
- `PATCH /agents/{id} workflows`
- `PATCH /conversations/uploaded/{id} metadata`
- `PATCH /personas/background-sounds/{id} acoustic_source_type`
- `PATCH /personas/{id} audio_degradation`
- `PATCH /personas/{id} background_sound_volume`
- `PATCH /personas/{id} hold_music_timeout_seconds`
- `PATCH /personas/{id} situate_speaker`
- `PATCH /personas/{id} tags`
- `PATCH /personas/{id} voice_speed`
- `PATCH /personas/{id} voice_volume`
- `PATCH /metrics/{id} ivr_flow`
- `PATCH /reports/{id} simulation_output_ids`
- `PATCH /reports/{id} source_human_review_project_id`
- `PATCH /reports/{id} view_config`
Expand All @@ -167,14 +163,7 @@ Catalog: https://api.coval.dev/v1/openapi
- `PATCH /run-templates/{id} test_set_id`
- `PATCH /test-sets/{id} tags`
- `POST /conversations/uploaded:submit tags`
- `POST /personas audio_degradation`
- `POST /personas background_sound_volume`
- `POST /personas hold_music_timeout_seconds`
- `POST /personas situate_speaker`
- `POST /personas tags`
- `POST /personas voice_speed`
- `POST /personas voice_volume`
- `POST /personas/background-sounds acoustic_source_type`
- `POST /metrics ivr_flow`
- `POST /reports simulation_output_ids`
- `POST /reports source_human_review_project_id`
- `POST /review-projects blind_labeling_shown_metric_ids`
Expand All @@ -184,4 +173,5 @@ Catalog: https://api.coval.dev/v1/openapi
- `POST /run-templates persona_id`
- `POST /run-templates tags`
- `POST /run-templates test_set_id`
- `POST /runs config_overrides`
- `POST /test-sets tags`
80 changes: 0 additions & 80 deletions api-coverage.toml
Original file line number Diff line number Diff line change
Expand Up @@ -421,46 +421,6 @@ operation = "PATCH /conversations/uploaded/{conversation_id}"
field = "metadata"
reason = "The additive customer-metadata patch path is served but unmodeled; tracked under COVAL-5823."

[[known_field_gap]]
operation = "PATCH /personas/background-sounds/{background_sound_id}"
field = "acoustic_source_type"
reason = "Background-sound acoustic source is served but unmodeled; tracked under COVAL-5821."

[[known_field_gap]]
operation = "PATCH /personas/{persona_id}"
field = "audio_degradation"
reason = "Persona audio and placement fields are served but unmodeled; tracked under COVAL-5821."

[[known_field_gap]]
operation = "PATCH /personas/{persona_id}"
field = "background_sound_volume"
reason = "Persona audio and placement fields are served but unmodeled; tracked under COVAL-5821."

[[known_field_gap]]
operation = "PATCH /personas/{persona_id}"
field = "hold_music_timeout_seconds"
reason = "Persona audio and placement fields are served but unmodeled; tracked under COVAL-5821."

[[known_field_gap]]
operation = "PATCH /personas/{persona_id}"
field = "situate_speaker"
reason = "Persona audio and placement fields are served but unmodeled; tracked under COVAL-5821."

[[known_field_gap]]
operation = "PATCH /personas/{persona_id}"
field = "tags"
reason = "Resource tagging on write is served but unmodeled; tracked under COVAL-5823."

[[known_field_gap]]
operation = "PATCH /personas/{persona_id}"
field = "voice_speed"
reason = "Persona audio and placement fields are served but unmodeled; tracked under COVAL-5821."

[[known_field_gap]]
operation = "PATCH /personas/{persona_id}"
field = "voice_volume"
reason = "Persona audio and placement fields are served but unmodeled; tracked under COVAL-5821."

[[known_field_gap]]
operation = "PATCH /reports/{report_id}"
field = "simulation_output_ids"
Expand Down Expand Up @@ -536,46 +496,6 @@ operation = "POST /conversations/uploaded:submit"
field = "tags"
reason = "Resource tagging on write is served but unmodeled; tracked under COVAL-5823."

[[known_field_gap]]
operation = "POST /personas"
field = "audio_degradation"
reason = "Persona audio and placement fields are served but unmodeled; tracked under COVAL-5821."

[[known_field_gap]]
operation = "POST /personas"
field = "background_sound_volume"
reason = "Persona audio and placement fields are served but unmodeled; tracked under COVAL-5821."

[[known_field_gap]]
operation = "POST /personas"
field = "hold_music_timeout_seconds"
reason = "Persona audio and placement fields are served but unmodeled; tracked under COVAL-5821."

[[known_field_gap]]
operation = "POST /personas"
field = "situate_speaker"
reason = "Persona audio and placement fields are served but unmodeled; tracked under COVAL-5821."

[[known_field_gap]]
operation = "POST /personas"
field = "tags"
reason = "Resource tagging on write is served but unmodeled; tracked under COVAL-5823."

[[known_field_gap]]
operation = "POST /personas"
field = "voice_speed"
reason = "Persona audio and placement fields are served but unmodeled; tracked under COVAL-5821."

[[known_field_gap]]
operation = "POST /personas"
field = "voice_volume"
reason = "Persona audio and placement fields are served but unmodeled; tracked under COVAL-5821."

[[known_field_gap]]
operation = "POST /personas/background-sounds"
field = "acoustic_source_type"
reason = "Background-sound acoustic source is served but unmodeled; tracked under COVAL-5821."

[[known_field_gap]]
operation = "POST /reports"
field = "simulation_output_ids"
Expand Down
12 changes: 12 additions & 0 deletions src/client/models/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,15 @@ impl ListParams {
}
}
}

/// Deserializes a present field into `Some(..)` even when its value is null, so
/// callers can tell "field omitted" from "field explicitly cleared". Pair it with
/// `Option<Option<T>>` on a PATCH request wherever the API treats null as a clear;
/// `skip_serializing_if` alone would turn a deliberate clear into a no-op.
pub(crate) fn explicit_option<'de, D, T>(deserializer: D) -> Result<Option<Option<T>>, D::Error>
where
D: serde::Deserializer<'de>,
T: Deserialize<'de>,
{
Option::<T>::deserialize(deserializer).map(Some)
}
68 changes: 68 additions & 0 deletions src/client/models/persona.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,24 @@ pub struct CreatePersonaRequest {
pub conversation_initiation: Option<String>,
#[serde(alias = "multiLanguageStt", skip_serializing_if = "Option::is_none")]
pub multi_language_stt: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub background_sound_volume: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub voice_volume: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub voice_speed: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hold_music_timeout_seconds: Option<f64>,
/// Mutually exclusive with `audio_degradation`.
#[serde(skip_serializing_if = "Option::is_none")]
pub situate_speaker: Option<String>,
/// `{"preset": "...", "preset_version": "..."}`; mutually exclusive with
/// `situate_speaker`.
#[serde(skip_serializing_if = "Option::is_none")]
pub audio_degradation: Option<serde_json::Value>,
/// Tag names. Omitted leaves tags unchanged; an empty list clears them.
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<String>>,
}

#[derive(Debug, Default, Serialize, Deserialize)]
Expand All @@ -98,6 +116,45 @@ pub struct UpdatePersonaRequest {
pub conversation_initiation: Option<String>,
#[serde(alias = "multiLanguageStt", skip_serializing_if = "Option::is_none")]
pub multi_language_stt: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub background_sound_volume: Option<f64>,
// The API deletes the stored value when these arrive as an explicit null, so
// absent and null have to stay distinguishable.
#[serde(
default,
deserialize_with = "super::explicit_option",
skip_serializing_if = "Option::is_none"
)]
pub voice_volume: Option<Option<f64>>,
#[serde(
default,
deserialize_with = "super::explicit_option",
skip_serializing_if = "Option::is_none"
)]
pub voice_speed: Option<Option<f64>>,
#[serde(
default,
deserialize_with = "super::explicit_option",
skip_serializing_if = "Option::is_none"
)]
pub hold_music_timeout_seconds: Option<Option<f64>>,
/// Mutually exclusive with `audio_degradation`; an explicit null clears it.
#[serde(
default,
deserialize_with = "super::explicit_option",
skip_serializing_if = "Option::is_none"
)]
pub situate_speaker: Option<Option<String>>,
/// Mutually exclusive with `situate_speaker`; an explicit null clears it.
#[serde(
default,
deserialize_with = "super::explicit_option",
skip_serializing_if = "Option::is_none"
)]
pub audio_degradation: Option<Option<serde_json::Value>>,
/// Tag names. Omitted leaves tags unchanged; an empty list clears them.
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<String>>,
}

#[derive(Debug, Deserialize)]
Expand Down Expand Up @@ -220,6 +277,10 @@ pub struct CreateBackgroundSoundRequest {
pub content_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_volume: Option<f64>,
/// `ambient` mixes the sound as room ambience; `point_source` renders it as a
/// located source.
#[serde(skip_serializing_if = "Option::is_none")]
pub acoustic_source_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<BTreeMap<String, String>>,
}
Expand Down Expand Up @@ -251,6 +312,13 @@ pub struct UpdateBackgroundSoundRequest {
pub display_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_volume: Option<f64>,
/// An explicit null clears the stored rendering behavior.
#[serde(
default,
deserialize_with = "super::explicit_option",
skip_serializing_if = "Option::is_none"
)]
pub acoustic_source_type: Option<Option<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<BackgroundSoundUpdateStatus>,
}
Expand Down
12 changes: 1 addition & 11 deletions src/client/models/test_case.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ pub struct UpdateTestCaseRequest {
/// JSON null clears the stored turns, so absent and null must stay distinct.
#[serde(
default,
deserialize_with = "explicit_option",
deserialize_with = "super::explicit_option",
skip_serializing_if = "Option::is_none"
)]
pub script_turns: Option<Option<Vec<serde_json::Value>>>,
Expand All @@ -91,16 +91,6 @@ pub struct UpdateTestCaseRequest {
pub user_notes: Option<String>,
}

/// Deserializes a present field into `Some(..)` even when its value is null, so
/// callers can tell "field omitted" from "field explicitly cleared".
fn explicit_option<'de, D, T>(deserializer: D) -> Result<Option<Option<T>>, D::Error>
where
D: serde::Deserializer<'de>,
T: Deserialize<'de>,
{
Option::<T>::deserialize(deserializer).map(Some)
}

#[derive(Debug, Deserialize)]
pub struct ListTestCasesResponse {
pub test_cases: Vec<TestCase>,
Expand Down
Loading
Loading