fix: align Companion REST DTO contracts
This commit is contained in:
@@ -1,77 +1,14 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use workspace_api::{
|
||||
CompanionLifecycleState, CompanionMessageDisposition, CompanionTransportSummary, Diagnostic,
|
||||
DiagnosticSeverity,
|
||||
};
|
||||
|
||||
use crate::hosts::{DiagnosticSeverity, RuntimeDiagnostic, WorkerSummary};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CompanionState {
|
||||
Disabled,
|
||||
Rejected,
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct CompanionStatusResponse {
|
||||
pub state: CompanionState,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub worker: Option<WorkerSummary>,
|
||||
pub transport: CompanionTransportSummary,
|
||||
pub diagnostics: Vec<RuntimeDiagnostic>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct CompanionTransportSummary {
|
||||
pub kind: String,
|
||||
pub completion: String,
|
||||
pub limitation: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
||||
pub struct CompanionMessageRequest {
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Default)]
|
||||
pub struct CompanionCancelRequest {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct CompanionMessageResponse {
|
||||
pub state: CompanionState,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub worker: Option<WorkerSummary>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub user_item: Option<CompanionTranscriptItem>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub assistant_item: Option<CompanionTranscriptItem>,
|
||||
pub transcript: CompanionTranscriptProjection,
|
||||
pub diagnostics: Vec<RuntimeDiagnostic>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct CompanionTranscriptProjection {
|
||||
pub state: CompanionState,
|
||||
pub start: usize,
|
||||
pub limit: usize,
|
||||
pub total_items: usize,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub next_start: Option<usize>,
|
||||
pub items: Vec<CompanionTranscriptItem>,
|
||||
pub diagnostics: Vec<RuntimeDiagnostic>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct CompanionTranscriptItem {
|
||||
pub sequence: u64,
|
||||
pub role: String,
|
||||
pub content: String,
|
||||
pub created_at: String,
|
||||
pub source: String,
|
||||
pub status: String,
|
||||
}
|
||||
pub use workspace_api::{
|
||||
CompanionCancelRequest, CompanionMessageRequest, CompanionMessageResponse,
|
||||
CompanionStatusResponse, CompanionTranscriptProjection,
|
||||
};
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct CompanionConsole;
|
||||
|
||||
impl CompanionConsole {
|
||||
@@ -81,68 +18,50 @@ impl CompanionConsole {
|
||||
|
||||
pub fn status(&self) -> CompanionStatusResponse {
|
||||
CompanionStatusResponse {
|
||||
state: CompanionState::Disabled,
|
||||
state: CompanionLifecycleState::Stopped,
|
||||
worker: None,
|
||||
transport: disabled_transport(),
|
||||
transport: CompanionTransportSummary {
|
||||
mode: "disabled".to_string(),
|
||||
available: false,
|
||||
},
|
||||
diagnostics: vec![disabled_diagnostic()],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transcript(&self, start: usize, limit: usize) -> CompanionTranscriptProjection {
|
||||
CompanionTranscriptProjection {
|
||||
state: CompanionState::Disabled,
|
||||
state: CompanionLifecycleState::Stopped,
|
||||
start,
|
||||
limit,
|
||||
total_items: 0,
|
||||
next_start: None,
|
||||
total: 0,
|
||||
next: None,
|
||||
items: Vec::new(),
|
||||
diagnostics: vec![disabled_diagnostic()],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_message(&self, _request: CompanionMessageRequest) -> CompanionMessageResponse {
|
||||
disabled_message_response(CompanionState::Rejected)
|
||||
disabled_message_response()
|
||||
}
|
||||
|
||||
pub fn cancel(&self, _request: CompanionCancelRequest) -> CompanionMessageResponse {
|
||||
disabled_message_response(CompanionState::Cancelled)
|
||||
disabled_message_response()
|
||||
}
|
||||
}
|
||||
|
||||
fn disabled_message_response(state: CompanionState) -> CompanionMessageResponse {
|
||||
fn disabled_message_response() -> CompanionMessageResponse {
|
||||
CompanionMessageResponse {
|
||||
state,
|
||||
worker: None,
|
||||
user_item: None,
|
||||
assistant_item: None,
|
||||
transcript: CompanionTranscriptProjection {
|
||||
state: CompanionState::Disabled,
|
||||
start: 0,
|
||||
limit: 200,
|
||||
total_items: 0,
|
||||
next_start: None,
|
||||
items: Vec::new(),
|
||||
diagnostics: vec![disabled_diagnostic()],
|
||||
},
|
||||
diagnostics: vec![disabled_diagnostic()],
|
||||
}
|
||||
}
|
||||
|
||||
fn disabled_transport() -> CompanionTransportSummary {
|
||||
CompanionTransportSummary {
|
||||
kind: "none".to_string(),
|
||||
completion: "disabled".to_string(),
|
||||
limitation:
|
||||
"Workspace Companion auto-start has been removed; create an explicit Worker instead."
|
||||
.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn disabled_diagnostic() -> RuntimeDiagnostic {
|
||||
RuntimeDiagnostic {
|
||||
code: "companion_disabled".to_string(),
|
||||
severity: DiagnosticSeverity::Info,
|
||||
message: "Workspace Companion auto-start is disabled; create an explicit Worker instead."
|
||||
state: CompanionMessageDisposition::Rejected,
|
||||
message: "Workspace Companion auto-start is disabled; create or select an explicit Worker instead."
|
||||
.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn disabled_diagnostic() -> Diagnostic {
|
||||
Diagnostic {
|
||||
code: "companion_disabled".to_string(),
|
||||
severity: DiagnosticSeverity::Info,
|
||||
message:
|
||||
"Workspace Companion auto-start was removed; use the explicit Worker lifecycle instead."
|
||||
.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11084,33 +11084,43 @@ async fn get_workspace(State(api): State<WorkspaceApi>) -> ApiResult<Json<Worksp
|
||||
}
|
||||
|
||||
fn companion_console_extension_point(status: &CompanionStatusResponse) -> ExtensionPointState {
|
||||
let completion = status.transport.completion.clone();
|
||||
let note = match completion.as_str() {
|
||||
"connected" => "Workspace Companion is input-capable and browser input is dispatched through the normal Worker runtime path.".to_string(),
|
||||
"not_input_capable" => {
|
||||
let diagnostic_codes = status
|
||||
.diagnostics
|
||||
.iter()
|
||||
.map(|diagnostic| diagnostic.code.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
if diagnostic_codes.is_empty() {
|
||||
"Workspace Companion is not input-capable; check provider, config, profile, secret, and authority diagnostics.".to_string()
|
||||
} else {
|
||||
format!(
|
||||
"Workspace Companion is not input-capable; check typed diagnostics: {diagnostic_codes}."
|
||||
)
|
||||
}
|
||||
}
|
||||
"disabled" => "Workspace Companion auto-start has been removed; create an explicit Worker instead.".to_string(),
|
||||
other => format!(
|
||||
"Workspace Companion transport reports {other}; browser input follows the Companion Worker runtime capability state."
|
||||
),
|
||||
let extension_status = match status.state {
|
||||
workspace_api::CompanionLifecycleState::Idle => "idle",
|
||||
workspace_api::CompanionLifecycleState::Running => "running",
|
||||
workspace_api::CompanionLifecycleState::Stopped => "stopped",
|
||||
}
|
||||
.to_string();
|
||||
let diagnostic_codes = status
|
||||
.diagnostics
|
||||
.iter()
|
||||
.map(|diagnostic| diagnostic.code.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let note = if status.transport.available {
|
||||
"Workspace Companion is input-capable and browser input is dispatched through the normal Worker runtime path."
|
||||
.to_string()
|
||||
} else if diagnostic_codes.is_empty() {
|
||||
"Workspace Companion is unavailable; create or select an explicit Worker instead."
|
||||
.to_string()
|
||||
} else {
|
||||
format!("Workspace Companion is unavailable; check typed diagnostics: {diagnostic_codes}.")
|
||||
};
|
||||
ExtensionPointState {
|
||||
status: completion,
|
||||
status: extension_status,
|
||||
note,
|
||||
diagnostics: status.diagnostics.clone(),
|
||||
diagnostics: status
|
||||
.diagnostics
|
||||
.iter()
|
||||
.map(|diagnostic| RuntimeDiagnostic {
|
||||
code: diagnostic.code.clone(),
|
||||
severity: match diagnostic.severity {
|
||||
workspace_api::DiagnosticSeverity::Info => DiagnosticSeverity::Info,
|
||||
workspace_api::DiagnosticSeverity::Warning => DiagnosticSeverity::Warning,
|
||||
workspace_api::DiagnosticSeverity::Error => DiagnosticSeverity::Error,
|
||||
},
|
||||
message: diagnostic.message.clone(),
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24137,10 +24147,10 @@ mod tests {
|
||||
);
|
||||
|
||||
let companion_status = get_json(app.clone(), "/api/companion/status").await;
|
||||
assert_eq!(companion_status["state"], "disabled");
|
||||
assert_eq!(companion_status["state"], "stopped");
|
||||
assert!(companion_status["worker"].is_null());
|
||||
assert_eq!(companion_status["transport"]["kind"], "none");
|
||||
assert_eq!(companion_status["transport"]["completion"], "disabled");
|
||||
assert_eq!(companion_status["transport"]["mode"], "disabled");
|
||||
assert_eq!(companion_status["transport"]["available"], false);
|
||||
assert!(!companion_status.to_string().contains("/workspace/demo"));
|
||||
|
||||
let companion_message = post_json(
|
||||
@@ -24150,16 +24160,14 @@ mod tests {
|
||||
)
|
||||
.await;
|
||||
assert_eq!(companion_message["state"], "rejected");
|
||||
assert_eq!(
|
||||
companion_message["diagnostics"][0]["code"],
|
||||
"companion_disabled"
|
||||
);
|
||||
assert!(companion_message["user_item"].is_null());
|
||||
assert!(companion_message["assistant_item"].is_null());
|
||||
assert!(companion_message.get("accepted").is_none());
|
||||
assert!(companion_message.get("diagnostics").is_none());
|
||||
assert!(companion_message.get("user_item").is_none());
|
||||
assert!(companion_message.get("assistant_item").is_none());
|
||||
assert!(!companion_message.to_string().contains("/workspace/demo"));
|
||||
|
||||
let companion_transcript = get_json(app.clone(), "/api/companion/transcript").await;
|
||||
assert_eq!(companion_transcript["total_items"], 0);
|
||||
assert_eq!(companion_transcript["total"], 0);
|
||||
|
||||
let host_workers = get_json(app.clone(), &format!("/api/hosts/{host_id}/workers")).await;
|
||||
assert!(
|
||||
@@ -24264,7 +24272,7 @@ mod tests {
|
||||
|
||||
let workspace = get_json(app.clone(), "/api/workspace").await;
|
||||
let workspace_companion = &workspace["extension_points"]["companion_console"];
|
||||
assert_eq!(workspace_companion["status"], "disabled");
|
||||
assert_eq!(workspace_companion["status"], "stopped");
|
||||
assert_eq!(
|
||||
workspace_companion["diagnostics"][0]["code"],
|
||||
"companion_disabled"
|
||||
@@ -24273,12 +24281,13 @@ mod tests {
|
||||
workspace_companion["note"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.contains("auto-start has been removed")
|
||||
.contains("typed diagnostics")
|
||||
);
|
||||
|
||||
let status = get_json(app.clone(), "/api/companion/status").await;
|
||||
assert_eq!(status["state"], "disabled");
|
||||
assert_eq!(status["transport"]["completion"], "disabled");
|
||||
assert_eq!(status["state"], "stopped");
|
||||
assert_eq!(status["transport"]["mode"], "disabled");
|
||||
assert_eq!(status["transport"]["available"], false);
|
||||
assert!(status["worker"].is_null());
|
||||
|
||||
let response = post_json(
|
||||
@@ -24288,13 +24297,14 @@ mod tests {
|
||||
)
|
||||
.await;
|
||||
assert_eq!(response["state"], "rejected");
|
||||
assert_eq!(response["diagnostics"][0]["code"], "companion_disabled");
|
||||
assert!(response["user_item"].is_null());
|
||||
assert!(response["assistant_item"].is_null());
|
||||
assert!(response.get("accepted").is_none());
|
||||
assert!(response.get("diagnostics").is_none());
|
||||
assert!(response.get("user_item").is_none());
|
||||
assert!(response.get("assistant_item").is_none());
|
||||
|
||||
let transcript = get_json(app.clone(), "/api/companion/transcript").await;
|
||||
assert_eq!(transcript["state"], "disabled");
|
||||
assert_eq!(transcript["total_items"], 0);
|
||||
assert_eq!(transcript["state"], "stopped");
|
||||
assert_eq!(transcript["total"], 0);
|
||||
|
||||
let workers = get_json(app, "/api/workers").await;
|
||||
assert!(
|
||||
|
||||
Reference in New Issue
Block a user