Migrate diagnostics to JSON report (#9964)
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
This commit is contained in:
Generated
-1
@@ -4886,7 +4886,6 @@ dependencies = [
|
||||
"which 8.0.3",
|
||||
"winapi",
|
||||
"wiremock",
|
||||
"zip 8.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -580,11 +580,9 @@ enum SessionCommand {
|
||||
},
|
||||
#[command(name = "diagnostics")]
|
||||
Diagnostics {
|
||||
/// Session identifier for generating diagnostics
|
||||
#[command(flatten)]
|
||||
identifier: Option<Identifier>,
|
||||
|
||||
/// Output path for the diagnostics zip file (optional, defaults to current directory)
|
||||
#[arg(short = 'o', long)]
|
||||
output: Option<PathBuf>,
|
||||
},
|
||||
|
||||
@@ -7,7 +7,9 @@ use etcetera::home_dir;
|
||||
use goose::config::Config;
|
||||
#[cfg(feature = "nostr")]
|
||||
use goose::session::nostr_share;
|
||||
use goose::session::{generate_diagnostics, Session, SessionManager, SessionType};
|
||||
use goose::session::{
|
||||
generate_diagnostics, DiagnosticsLevel, Session, SessionManager, SessionType,
|
||||
};
|
||||
use goose::utils::safe_truncate;
|
||||
use regex::Regex;
|
||||
use std::fs;
|
||||
@@ -322,24 +324,27 @@ pub async fn handle_session_import(input: String, nostr: bool) -> Result<()> {
|
||||
|
||||
pub async fn handle_diagnostics(session_id: &str, output_path: Option<PathBuf>) -> Result<()> {
|
||||
println!(
|
||||
"Generating diagnostics bundle for session '{}'...",
|
||||
"Generating diagnostics report for session '{}'...",
|
||||
session_id
|
||||
);
|
||||
|
||||
let session_manager = SessionManager::instance();
|
||||
let diagnostics_data = generate_diagnostics(&session_manager, session_id)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to write to generate diagnostics bundle for session '{}'",
|
||||
session_id
|
||||
)
|
||||
})?;
|
||||
let diagnostics_report =
|
||||
generate_diagnostics(&session_manager, session_id, DiagnosticsLevel::Full)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to generate diagnostics report for session '{}'",
|
||||
session_id
|
||||
)
|
||||
})?;
|
||||
let diagnostics_data = serde_json::to_vec_pretty(&diagnostics_report)
|
||||
.context("Failed to serialize diagnostics report")?;
|
||||
|
||||
let output_file = if let Some(path) = output_path {
|
||||
path.clone()
|
||||
} else {
|
||||
PathBuf::from(format!("diagnostics_{}.zip", session_id))
|
||||
PathBuf::from(format!("diagnostics_{}.json", session_id))
|
||||
};
|
||||
|
||||
let mut file = fs::File::create(&output_file).context(format!(
|
||||
@@ -350,7 +355,7 @@ pub async fn handle_diagnostics(session_id: &str, output_path: Option<PathBuf>)
|
||||
file.write_all(&diagnostics_data)
|
||||
.context("Failed to write diagnostics data")?;
|
||||
|
||||
println!("Diagnostics bundle saved to: {}", output_file.display());
|
||||
println!("Diagnostics report saved to: {}", output_file.display());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -280,9 +280,15 @@ pub async fn handle_term_run(prompt: Vec<String>) -> Result<()> {
|
||||
};
|
||||
|
||||
if let Some(oldest_user) = user_messages_after_last_assistant.last() {
|
||||
session_manager
|
||||
.truncate_conversation(&session_id, oldest_user.created)
|
||||
.await?;
|
||||
if let Some(message_id) = oldest_user.id.as_deref() {
|
||||
session_manager
|
||||
.truncate_conversation_from_message(&session_id, message_id)
|
||||
.await?;
|
||||
} else {
|
||||
session_manager
|
||||
.truncate_conversation(&session_id, oldest_user.created)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
let prompt_with_context = if user_messages_after_last_assistant.is_empty() {
|
||||
|
||||
@@ -165,6 +165,31 @@ pub struct SteerSessionResponse {
|
||||
pub message_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
|
||||
#[request(
|
||||
method = "_goose/unstable/diagnostics/get",
|
||||
response = DiagnosticsGetResponse
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DiagnosticsGetRequest {
|
||||
pub session_id: String,
|
||||
#[serde(default)]
|
||||
pub level: DiagnosticsReportLevel,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DiagnosticsReportLevel {
|
||||
#[default]
|
||||
Summary,
|
||||
Full,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcResponse)]
|
||||
pub struct DiagnosticsGetResponse {
|
||||
pub report: serde_json::Value,
|
||||
}
|
||||
|
||||
/// Delete a session.
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
|
||||
#[request(method = "session/delete", response = EmptyResponse)]
|
||||
|
||||
@@ -7,7 +7,11 @@ use goose::conversation::token_usage::Usage;
|
||||
use goose::conversation::Conversation;
|
||||
use goose::download_manager::{DownloadProgress, DownloadStatus};
|
||||
use goose::providers::base::{ConfigKey, ModelInfo, ProviderMetadata, ProviderType};
|
||||
use goose::session::{Session, SessionType, SystemInfo};
|
||||
use goose::session::{
|
||||
DiagnosticsConfig, DiagnosticsError, DiagnosticsExtensions, DiagnosticsLevel, DiagnosticsLogs,
|
||||
DiagnosticsPrompt, DiagnosticsReport, DiagnosticsScheduledRecipe, DiagnosticsTextFile, Session,
|
||||
SessionType, SystemInfo,
|
||||
};
|
||||
use goose_providers::model::ModelConfig;
|
||||
use goose_providers::permission::Permission;
|
||||
use goose_providers::permission::PrincipalType;
|
||||
@@ -583,6 +587,15 @@ derive_utoipa!(IconTheme as IconThemeSchema);
|
||||
goose_providers::goose_mode::GooseMode,
|
||||
SessionType,
|
||||
SystemInfo,
|
||||
DiagnosticsConfig,
|
||||
DiagnosticsError,
|
||||
DiagnosticsExtensions,
|
||||
DiagnosticsLevel,
|
||||
DiagnosticsLogs,
|
||||
DiagnosticsPrompt,
|
||||
DiagnosticsReport,
|
||||
DiagnosticsScheduledRecipe,
|
||||
DiagnosticsTextFile,
|
||||
Conversation,
|
||||
IconSchema,
|
||||
IconThemeSchema,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use axum::body::Body;
|
||||
use axum::extract::State;
|
||||
use axum::http::HeaderValue;
|
||||
use axum::response::IntoResponse;
|
||||
use axum::{extract::Path, http::StatusCode, routing::get, Json, Router};
|
||||
use goose::session::{generate_diagnostics, get_system_info, SystemInfo};
|
||||
use axum::extract::{Path, Query, State};
|
||||
use axum::{http::StatusCode, routing::get, Json, Router};
|
||||
use goose::session::{
|
||||
generate_diagnostics, get_system_info, DiagnosticsLevel, DiagnosticsReport, SystemInfo,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::state::AppState;
|
||||
@@ -26,34 +26,33 @@ async fn system_info() -> Json<SystemInfo> {
|
||||
Json(get_system_info())
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, utoipa::IntoParams)]
|
||||
struct DiagnosticsQuery {
|
||||
level: Option<DiagnosticsLevel>,
|
||||
}
|
||||
|
||||
#[utoipa::path(get, path = "/diagnostics/{session_id}",
|
||||
params(
|
||||
DiagnosticsQuery,
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Diagnostics zip file", content_type = "application/zip", body = Vec<u8>),
|
||||
(status = 200, description = "Diagnostics report", body = DiagnosticsReport),
|
||||
(status = 500, description = "Failed to generate diagnostics"),
|
||||
)
|
||||
)]
|
||||
async fn diagnostics(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(session_id): Path<String>,
|
||||
) -> impl IntoResponse {
|
||||
match generate_diagnostics(state.session_manager(), &session_id).await {
|
||||
Ok(zip_data) => {
|
||||
let filename = format!("attachment; filename=\"diagnostics_{}.zip\"", session_id);
|
||||
let headers = [
|
||||
(
|
||||
http::header::CONTENT_TYPE,
|
||||
HeaderValue::from_static("application/zip"),
|
||||
),
|
||||
(
|
||||
http::header::CONTENT_DISPOSITION,
|
||||
HeaderValue::from_str(&filename).map_err(|_e| StatusCode::BAD_REQUEST)?,
|
||||
),
|
||||
];
|
||||
|
||||
Ok((headers, Body::from(zip_data)))
|
||||
}
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
Query(query): Query<DiagnosticsQuery>,
|
||||
) -> Result<Json<DiagnosticsReport>, StatusCode> {
|
||||
generate_diagnostics(
|
||||
state.session_manager(),
|
||||
&session_id,
|
||||
query.level.unwrap_or(DiagnosticsLevel::Full),
|
||||
)
|
||||
.await
|
||||
.map(Json)
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
pub fn routes(state: Arc<AppState>) -> Router {
|
||||
Router::new()
|
||||
|
||||
@@ -168,7 +168,6 @@ byteorder = { version = "1.5", default-features = false, features = ["std"], opt
|
||||
tokenizers = { version = "0.23", default-features = false, features = ["onig"], optional = true }
|
||||
symphonia = { version = "0.5", default-features = false, features = ["aac", "adpcm", "alac", "isomp4", "mkv", "mp3", "pcm", "vorbis", "wav"], optional = true }
|
||||
rubato = { version = "0.16", default-features = false, optional = true }
|
||||
zip = { workspace = true }
|
||||
sys-info = { version = "0.9", default-features = false }
|
||||
|
||||
llama-cpp-2 = { workspace = true, optional = true }
|
||||
|
||||
@@ -40,6 +40,11 @@
|
||||
"requestType": "SteerSessionRequest_unstable",
|
||||
"responseType": "SteerSessionResponse_unstable"
|
||||
},
|
||||
{
|
||||
"method": "_goose/unstable/diagnostics/get",
|
||||
"requestType": "DiagnosticsGetRequest_unstable",
|
||||
"responseType": "DiagnosticsGetResponse_unstable"
|
||||
},
|
||||
{
|
||||
"method": "session/delete",
|
||||
"requestType": "DeleteSessionRequest",
|
||||
|
||||
@@ -973,6 +973,41 @@
|
||||
"x-side": "agent",
|
||||
"x-method": "_goose/unstable/session/steer"
|
||||
},
|
||||
"DiagnosticsGetRequest_unstable": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sessionId": {
|
||||
"type": "string"
|
||||
},
|
||||
"level": {
|
||||
"$ref": "#/$defs/DiagnosticsReportLevel",
|
||||
"default": "summary"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sessionId"
|
||||
],
|
||||
"x-side": "agent",
|
||||
"x-method": "_goose/unstable/diagnostics/get"
|
||||
},
|
||||
"DiagnosticsReportLevel": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"summary",
|
||||
"full"
|
||||
]
|
||||
},
|
||||
"DiagnosticsGetResponse_unstable": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"report": {}
|
||||
},
|
||||
"required": [
|
||||
"report"
|
||||
],
|
||||
"x-side": "agent",
|
||||
"x-method": "_goose/unstable/diagnostics/get"
|
||||
},
|
||||
"DeleteSessionRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -4613,6 +4648,15 @@
|
||||
"description": "Params for _goose/unstable/session/steer",
|
||||
"title": "SteerSessionRequest_unstable"
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/$defs/DiagnosticsGetRequest_unstable"
|
||||
}
|
||||
],
|
||||
"description": "Params for _goose/unstable/diagnostics/get",
|
||||
"title": "DiagnosticsGetRequest_unstable"
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
@@ -5250,6 +5294,14 @@
|
||||
],
|
||||
"title": "SteerSessionResponse_unstable"
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/$defs/DiagnosticsGetResponse_unstable"
|
||||
}
|
||||
],
|
||||
"title": "DiagnosticsGetResponse_unstable"
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
|
||||
@@ -84,6 +84,7 @@ mod agent_requests;
|
||||
pub use agent_requests::agent_request_schemas;
|
||||
mod config;
|
||||
mod custom_dispatch;
|
||||
mod diagnostics;
|
||||
mod dictation;
|
||||
mod dispatch;
|
||||
mod elicitation;
|
||||
|
||||
@@ -82,6 +82,14 @@ impl GooseAcpAgent {
|
||||
self.on_steer_session(req).await
|
||||
}
|
||||
|
||||
#[custom_method(DiagnosticsGetRequest)]
|
||||
async fn dispatch_get_diagnostics(
|
||||
&self,
|
||||
req: DiagnosticsGetRequest,
|
||||
) -> Result<DiagnosticsGetResponse, agent_client_protocol::Error> {
|
||||
self.on_get_diagnostics(req).await
|
||||
}
|
||||
|
||||
#[custom_method(DeleteSessionRequest)]
|
||||
async fn dispatch_delete_session(
|
||||
&self,
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
use super::*;
|
||||
use crate::session::{generate_diagnostics, DiagnosticsLevel};
|
||||
|
||||
impl GooseAcpAgent {
|
||||
pub(super) async fn on_get_diagnostics(
|
||||
&self,
|
||||
req: DiagnosticsGetRequest,
|
||||
) -> Result<DiagnosticsGetResponse, agent_client_protocol::Error> {
|
||||
let level = match req.level {
|
||||
DiagnosticsReportLevel::Summary => DiagnosticsLevel::Summary,
|
||||
DiagnosticsReportLevel::Full => DiagnosticsLevel::Full,
|
||||
};
|
||||
let report = generate_diagnostics(&self.session_manager, &req.session_id, level)
|
||||
.await
|
||||
.internal_err()?;
|
||||
let report = serde_json::to_value(report).internal_err()?;
|
||||
|
||||
Ok(DiagnosticsGetResponse { report })
|
||||
}
|
||||
}
|
||||
@@ -6,14 +6,22 @@ use crate::providers::utils::LOGS_TO_KEEP;
|
||||
use crate::session::SessionManager;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::io::Cursor;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use utoipa::ToSchema;
|
||||
use zip::write::SimpleFileOptions;
|
||||
use zip::ZipWriter;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
const SERVER_LOG_TAIL_LINES: usize = 400;
|
||||
const LLM_LOG_MAX_BYTES: usize = 2 * 1024 * 1024;
|
||||
const CONFIG_MAX_BYTES: usize = 256 * 1024;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, ToSchema, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DiagnosticsLevel {
|
||||
#[default]
|
||||
Summary,
|
||||
Full,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, schemars::JsonSchema)]
|
||||
pub struct SystemInfo {
|
||||
pub app_version: String,
|
||||
pub os: String,
|
||||
@@ -24,6 +32,73 @@ pub struct SystemInfo {
|
||||
pub enabled_extensions: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DiagnosticsConfig {
|
||||
pub config_path: String,
|
||||
pub config_yaml: Option<String>,
|
||||
pub truncated: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DiagnosticsExtensions {
|
||||
pub enabled: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DiagnosticsTextFile {
|
||||
pub path: String,
|
||||
pub content: String,
|
||||
pub truncated: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, ToSchema, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DiagnosticsLogs {
|
||||
pub server: Option<DiagnosticsTextFile>,
|
||||
pub llm: Vec<DiagnosticsTextFile>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DiagnosticsPrompt {
|
||||
pub name: String,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DiagnosticsScheduledRecipe {
|
||||
pub path: String,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DiagnosticsError {
|
||||
pub path: Option<String>,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DiagnosticsReport {
|
||||
pub schema_version: u32,
|
||||
pub generated_at: String,
|
||||
pub level: DiagnosticsLevel,
|
||||
pub system: SystemInfo,
|
||||
pub config: Option<DiagnosticsConfig>,
|
||||
pub extensions: DiagnosticsExtensions,
|
||||
pub session: Option<serde_json::Value>,
|
||||
pub logs: DiagnosticsLogs,
|
||||
pub prompts: Vec<DiagnosticsPrompt>,
|
||||
pub schedule: Option<serde_json::Value>,
|
||||
pub scheduled_recipes: Vec<DiagnosticsScheduledRecipe>,
|
||||
pub errors: Vec<DiagnosticsError>,
|
||||
}
|
||||
|
||||
impl SystemInfo {
|
||||
pub fn collect() -> Self {
|
||||
let config = Config::global();
|
||||
@@ -86,6 +161,68 @@ pub fn latest_llm_log_path() -> Option<PathBuf> {
|
||||
path.exists().then_some(path)
|
||||
}
|
||||
|
||||
fn recent_llm_log_paths() -> Vec<PathBuf> {
|
||||
let logs_dir = Paths::in_state_dir("logs");
|
||||
let paths: Vec<_> = fs::read_dir(logs_dir)
|
||||
.ok()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|entry| entry.ok().map(|entry| entry.path()))
|
||||
.filter(|path| {
|
||||
path.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.is_some_and(|name| name.starts_with("llm_request.") && name.ends_with(".jsonl"))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let (mut numbered, mut temp): (Vec<_>, Vec<_>) = paths
|
||||
.into_iter()
|
||||
.partition(|path| llm_log_index(path).is_some());
|
||||
|
||||
numbered.sort_by_key(|path| llm_log_index(path).unwrap_or(usize::MAX));
|
||||
temp.sort_by(|left, right| {
|
||||
llm_log_modified(right)
|
||||
.cmp(&llm_log_modified(left))
|
||||
.then_with(|| llm_log_name(left).cmp(&llm_log_name(right)))
|
||||
});
|
||||
|
||||
if temp.is_empty() || numbered.len() < LOGS_TO_KEEP {
|
||||
numbered.extend(temp);
|
||||
numbered.truncate(LOGS_TO_KEEP);
|
||||
numbered
|
||||
} else {
|
||||
let temp_slots = 1;
|
||||
let numbered_slots = LOGS_TO_KEEP.saturating_sub(temp_slots);
|
||||
temp.truncate(temp_slots);
|
||||
numbered.truncate(numbered_slots);
|
||||
temp.extend(numbered);
|
||||
temp
|
||||
}
|
||||
}
|
||||
|
||||
fn llm_log_index(path: &std::path::Path) -> Option<usize> {
|
||||
let name = path
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.unwrap_or_default();
|
||||
name.strip_prefix("llm_request.")
|
||||
.and_then(|name| name.strip_suffix(".jsonl"))
|
||||
.and_then(|name| name.parse::<usize>().ok())
|
||||
}
|
||||
|
||||
fn llm_log_modified(path: &std::path::Path) -> std::time::SystemTime {
|
||||
path.metadata()
|
||||
.and_then(|metadata| metadata.modified())
|
||||
.unwrap_or(std::time::SystemTime::UNIX_EPOCH)
|
||||
}
|
||||
|
||||
fn llm_log_name(path: &std::path::Path) -> String {
|
||||
path.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.unwrap_or_default()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
pub fn read_tail(path: &std::path::Path, max_lines: usize) -> Option<String> {
|
||||
let content = fs::read_to_string(path).ok()?;
|
||||
let lines: Vec<&str> = content.lines().collect();
|
||||
@@ -128,6 +265,10 @@ pub fn read_capped(path: &std::path::Path, max_bytes: usize) -> Option<String> {
|
||||
))
|
||||
}
|
||||
|
||||
fn was_truncated(content: &str) -> bool {
|
||||
content.contains("... (") && content.contains(" bytes omitted) ...")
|
||||
}
|
||||
|
||||
fn latest_entry_by_name(dir: &std::path::Path) -> Option<PathBuf> {
|
||||
let mut entries: Vec<_> = fs::read_dir(dir).ok()?.filter_map(|e| e.ok()).collect();
|
||||
entries.sort_by_key(|e| e.file_name());
|
||||
@@ -137,81 +278,135 @@ fn latest_entry_by_name(dir: &std::path::Path) -> Option<PathBuf> {
|
||||
pub async fn generate_diagnostics(
|
||||
session_manager: &SessionManager,
|
||||
session_id: &str,
|
||||
) -> anyhow::Result<Vec<u8>> {
|
||||
let logs_dir = Paths::in_state_dir("logs");
|
||||
let config_dir = Paths::config_dir();
|
||||
let config_path = config_dir.join("config.yaml");
|
||||
level: DiagnosticsLevel,
|
||||
) -> anyhow::Result<DiagnosticsReport> {
|
||||
let config_path = config_path();
|
||||
let data_dir = Paths::data_dir();
|
||||
|
||||
let system_info = SystemInfo::collect();
|
||||
let is_full = matches!(level, DiagnosticsLevel::Full);
|
||||
let mut errors = Vec::new();
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
{
|
||||
let mut zip = ZipWriter::new(Cursor::new(&mut buffer));
|
||||
let options =
|
||||
SimpleFileOptions::default().compression_method(zip::CompressionMethod::Deflated);
|
||||
|
||||
let mut log_files: Vec<_> = fs::read_dir(&logs_dir)?
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| e.path().extension().is_some_and(|ext| ext == "jsonl"))
|
||||
.collect();
|
||||
|
||||
log_files.sort_by_key(|e| e.metadata().ok().and_then(|m| m.modified().ok()));
|
||||
|
||||
for entry in log_files.iter().rev().take(LOGS_TO_KEEP) {
|
||||
let path = entry.path();
|
||||
let name = path.file_name().unwrap().to_str().unwrap();
|
||||
zip.start_file(format!("logs/{}", name), options)?;
|
||||
zip.write_all(&fs::read(&path)?)?;
|
||||
}
|
||||
|
||||
if let Some(server_log) = latest_server_log_path() {
|
||||
if let Ok(content) = fs::read(&server_log) {
|
||||
let name = server_log.file_name().unwrap().to_str().unwrap();
|
||||
zip.start_file(format!("logs/server/{}", name), options)?;
|
||||
zip.write_all(&content)?;
|
||||
}
|
||||
}
|
||||
|
||||
let session = if is_full {
|
||||
let session_data = session_manager.export_session(session_id).await?;
|
||||
zip.start_file("session.json", options)?;
|
||||
zip.write_all(session_data.as_bytes())?;
|
||||
Some(serde_json::from_str(&session_data)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if config_path.exists() {
|
||||
zip.start_file("config.yaml", options)?;
|
||||
zip.write_all(&fs::read(&config_path)?)?;
|
||||
let config = if is_full {
|
||||
let config_yaml = if config_path.exists() {
|
||||
read_capped(&config_path, CONFIG_MAX_BYTES)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let truncated = config_yaml.as_deref().is_some_and(was_truncated);
|
||||
Some(DiagnosticsConfig {
|
||||
config_path: config_path.display().to_string(),
|
||||
config_yaml,
|
||||
truncated,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let logs = if is_full {
|
||||
DiagnosticsLogs {
|
||||
server: latest_server_log_path().and_then(|path| {
|
||||
read_tail(&path, SERVER_LOG_TAIL_LINES).map(|content| DiagnosticsTextFile {
|
||||
path: path.display().to_string(),
|
||||
content,
|
||||
truncated: true,
|
||||
})
|
||||
}),
|
||||
llm: recent_llm_log_paths()
|
||||
.into_iter()
|
||||
.filter_map(|path| {
|
||||
read_capped(&path, LLM_LOG_MAX_BYTES).map(|content| {
|
||||
let truncated = was_truncated(&content);
|
||||
DiagnosticsTextFile {
|
||||
path: path.display().to_string(),
|
||||
content,
|
||||
truncated,
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
} else {
|
||||
DiagnosticsLogs::default()
|
||||
};
|
||||
|
||||
zip.start_file("system.txt", options)?;
|
||||
zip.write_all(system_info.to_text().as_bytes())?;
|
||||
let prompts = if is_full {
|
||||
list_templates()
|
||||
.into_iter()
|
||||
.map(|template| DiagnosticsPrompt {
|
||||
name: template.name,
|
||||
content: template.user_content.unwrap_or(template.default_content),
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
let schedule = if is_full {
|
||||
let schedule_json = data_dir.join("schedule.json");
|
||||
if schedule_json.exists() {
|
||||
zip.start_file("schedule.json", options)?;
|
||||
zip.write_all(&fs::read(&schedule_json)?)?;
|
||||
fs::read_to_string(&schedule_json).ok().and_then(|content| {
|
||||
match serde_json::from_str(&content) {
|
||||
Ok(value) => Some(value),
|
||||
Err(err) => {
|
||||
errors.push(DiagnosticsError {
|
||||
path: Some(schedule_json.display().to_string()),
|
||||
message: err.to_string(),
|
||||
});
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut scheduled_recipes = Vec::new();
|
||||
if is_full {
|
||||
let scheduled_recipes_dir = data_dir.join("scheduled_recipes");
|
||||
if scheduled_recipes_dir.exists() && scheduled_recipes_dir.is_dir() {
|
||||
for entry in fs::read_dir(&scheduled_recipes_dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.is_file() {
|
||||
let name = path.file_name().unwrap().to_str().unwrap();
|
||||
zip.start_file(format!("scheduled_recipes/{}", name), options)?;
|
||||
zip.write_all(&fs::read(&path)?)?;
|
||||
match fs::read_to_string(&path) {
|
||||
Ok(content) => scheduled_recipes.push(DiagnosticsScheduledRecipe {
|
||||
path: path.display().to_string(),
|
||||
content,
|
||||
}),
|
||||
Err(err) => errors.push(DiagnosticsError {
|
||||
path: Some(path.display().to_string()),
|
||||
message: err.to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for template in list_templates() {
|
||||
let content = template.user_content.unwrap_or(template.default_content);
|
||||
zip.start_file(format!("prompts/{}.txt", template.name), options)?;
|
||||
zip.write_all(content.as_bytes())?;
|
||||
}
|
||||
|
||||
zip.finish()?;
|
||||
}
|
||||
|
||||
Ok(buffer)
|
||||
Ok(DiagnosticsReport {
|
||||
schema_version: 1,
|
||||
generated_at: chrono::Utc::now().to_rfc3339(),
|
||||
level,
|
||||
system: system_info.clone(),
|
||||
config,
|
||||
extensions: DiagnosticsExtensions {
|
||||
enabled: system_info.enabled_extensions,
|
||||
},
|
||||
session,
|
||||
logs,
|
||||
prompts,
|
||||
schedule,
|
||||
scheduled_recipes,
|
||||
errors,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@ mod session_naming;
|
||||
|
||||
pub use diagnostics::{
|
||||
config_path, generate_diagnostics, get_system_info, latest_llm_log_path,
|
||||
latest_server_log_path, read_capped, read_tail, SystemInfo,
|
||||
latest_server_log_path, read_capped, read_tail, DiagnosticsConfig, DiagnosticsError,
|
||||
DiagnosticsExtensions, DiagnosticsLevel, DiagnosticsLogs, DiagnosticsPrompt, DiagnosticsReport,
|
||||
DiagnosticsScheduledRecipe, DiagnosticsTextFile, SystemInfo,
|
||||
};
|
||||
pub use extension_data::{EnabledExtensionsState, ExtensionData, ExtensionState, TodoState};
|
||||
pub use session_manager::{
|
||||
|
||||
@@ -451,6 +451,16 @@ impl SessionManager {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn truncate_conversation_from_message(
|
||||
&self,
|
||||
session_id: &str,
|
||||
message_id: &str,
|
||||
) -> Result<()> {
|
||||
self.storage
|
||||
.truncate_conversation_from_message(session_id, message_id)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn system_generated_name_update(
|
||||
&self,
|
||||
id: &str,
|
||||
@@ -1948,6 +1958,38 @@ impl SessionStorage {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn truncate_conversation_from_message(
|
||||
&self,
|
||||
session_id: &str,
|
||||
message_id: &str,
|
||||
) -> Result<()> {
|
||||
let pool = self.pool().await?;
|
||||
let mut tx = pool.begin_with("BEGIN IMMEDIATE").await?;
|
||||
|
||||
let boundary = sqlx::query_as::<_, (i64, i64)>(
|
||||
"SELECT id, created_timestamp FROM messages WHERE session_id = ? AND message_id = ? ORDER BY created_timestamp, id LIMIT 1",
|
||||
)
|
||||
.bind(session_id)
|
||||
.bind(message_id)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?;
|
||||
|
||||
if let Some((boundary_id, boundary_timestamp)) = boundary {
|
||||
sqlx::query(
|
||||
"DELETE FROM messages WHERE session_id = ? AND (created_timestamp > ? OR (created_timestamp = ? AND id >= ?))",
|
||||
)
|
||||
.bind(session_id)
|
||||
.bind(boundary_timestamp)
|
||||
.bind(boundary_timestamp)
|
||||
.bind(boundary_id)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
tx.commit().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn search_chat_history(
|
||||
&self,
|
||||
query: &str,
|
||||
@@ -2227,12 +2269,90 @@ mod tests {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn set_message_timestamp(
|
||||
sm: &SessionManager,
|
||||
session_id: &str,
|
||||
message_id: &str,
|
||||
timestamp: &str,
|
||||
) {
|
||||
let pool = sm.storage().pool().await.unwrap();
|
||||
let timestamp = chrono::DateTime::parse_from_rfc3339(timestamp).unwrap();
|
||||
let timestamp_string = timestamp.format("%Y-%m-%d %H:%M:%S").to_string();
|
||||
|
||||
sqlx::query(
|
||||
"UPDATE messages SET timestamp = ?, created_timestamp = ? WHERE session_id = ? AND message_id = ?",
|
||||
)
|
||||
.bind(×tamp_string)
|
||||
.bind(timestamp.timestamp())
|
||||
.bind(session_id)
|
||||
.bind(message_id)
|
||||
.execute(pool)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn add_user_message(sm: &SessionManager, session_id: &str) {
|
||||
sm.add_message(session_id, &Message::user().with_text("hello world"))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_truncate_conversation_from_message_keeps_same_second_previous_rows() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let sm = SessionManager::new(temp_dir.path().to_path_buf());
|
||||
let session = sm
|
||||
.create_session(
|
||||
temp_dir.path().to_path_buf(),
|
||||
"Same second truncation".to_string(),
|
||||
SessionType::User,
|
||||
GooseMode::default(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let timestamp = "2026-06-23T12:00:00Z";
|
||||
sm.add_message(
|
||||
&session.id,
|
||||
&Message::assistant()
|
||||
.with_text("assistant reply")
|
||||
.with_id("assistant"),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
set_message_timestamp(&sm, &session.id, "assistant", timestamp).await;
|
||||
|
||||
sm.add_message(
|
||||
&session.id,
|
||||
&Message::user()
|
||||
.with_text("terminal history")
|
||||
.with_id("terminal-history"),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
set_message_timestamp(&sm, &session.id, "terminal-history", timestamp).await;
|
||||
|
||||
sm.add_message(
|
||||
&session.id,
|
||||
&Message::user()
|
||||
.with_text("next prompt")
|
||||
.with_id("next-prompt"),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
set_message_timestamp(&sm, &session.id, "next-prompt", timestamp).await;
|
||||
|
||||
sm.truncate_conversation_from_message(&session.id, "terminal-history")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let reloaded = sm.get_session(&session.id, true).await.unwrap();
|
||||
let messages = reloaded.conversation.unwrap().messages().to_vec();
|
||||
assert_eq!(messages.len(), 1);
|
||||
assert_eq!(messages[0].id.as_deref(), Some("assistant"));
|
||||
assert_eq!(messages[0].as_concat_text(), "assistant reply");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_maybe_update_name_updates_eligible_session() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
|
||||
@@ -351,13 +351,13 @@ goose session export --path ./my-session.jsonl -o exported.md
|
||||
---
|
||||
|
||||
#### session diagnostics [options]
|
||||
Generate a comprehensive diagnostics bundle for troubleshooting issues with a specific session.
|
||||
Generate a comprehensive diagnostics JSON report for troubleshooting issues with a specific session.
|
||||
|
||||
**Options:**
|
||||
- **`--session-id <session_id>`**: Generate diagnostics for a specific session by ID
|
||||
- **`-n, --name <name>`**: Generate diagnostics for a specific session by name
|
||||
- **`--path <path>`**: Generate diagnostics for a specific session by file path (legacy)
|
||||
- **`-o, --output <file>`**: Save diagnostics bundle to a specific file path (default: `diagnostics_{session_id}.zip`)
|
||||
- **`-o, --output <file>`**: Save diagnostics report to a specific file path (default: `diagnostics_{session_id}.json`)
|
||||
|
||||
**What's included:**
|
||||
- **System Information**: App version, operating system, architecture, and timestamp
|
||||
@@ -374,18 +374,18 @@ goose session diagnostics --session-id 20251108_5
|
||||
goose session diagnostics -n my-project-session
|
||||
|
||||
# Save diagnostics to a custom location
|
||||
goose session diagnostics --session-id 20251108_5 -o /path/to/my-diagnostics.zip
|
||||
goose session diagnostics --session-id 20251108_5 -o /path/to/my-diagnostics.json
|
||||
|
||||
# Interactive selection (prompts you to choose a session)
|
||||
goose session diagnostics
|
||||
```
|
||||
|
||||
:::warning Privacy Notice
|
||||
Diagnostics bundles contain your session messages and system information. If your session includes sensitive data (API keys, personal information, proprietary code), review the contents before sharing publicly.
|
||||
Diagnostics reports contain your session messages and system information. If your session includes sensitive data (API keys, personal information, proprietary code), review the contents before sharing publicly.
|
||||
:::
|
||||
|
||||
:::tip
|
||||
Generate diagnostics before reporting bugs to provide technical details that help with faster resolution. The ZIP file can be attached to GitHub issues or shared with support.
|
||||
Generate diagnostics before reporting bugs to provide technical details that help with faster resolution. The JSON file can be attached to GitHub issues or shared with support.
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
@@ -12,13 +12,13 @@ goose provides several built-in features to help you get support, report issues,
|
||||
|
||||
| Feature | Purpose | Location | Output |
|
||||
|---------|---------|----------|---------|
|
||||
| **Diagnostics** | Generate troubleshooting data | Chat input toolbar | ZIP file with system info, logs, and session data |
|
||||
| **Diagnostics** | Generate troubleshooting data | Chat input toolbar | JSON report with system info, logs, and session data |
|
||||
| **Report a Bug** | Submit bug reports | Chat input toolbar OR Settings → App → Help & feedback | Opens GitHub issue template |
|
||||
| **Request a Feature** | Suggest new features | Settings → App → Help & feedback | Opens GitHub issue template |
|
||||
|
||||
## Diagnostics System
|
||||
|
||||
The diagnostics feature creates a comprehensive troubleshooting bundle that includes system information, session data, configuration files, and recent logs. This is invaluable for debugging issues or getting technical support.
|
||||
The diagnostics feature creates a comprehensive troubleshooting JSON report that includes system information, session data, configuration files, and recent logs. This is invaluable for debugging issues or getting technical support.
|
||||
|
||||
### Generating Diagnostics
|
||||
|
||||
@@ -27,8 +27,10 @@ The diagnostics feature creates a comprehensive troubleshooting bundle that incl
|
||||
1. In an active chat session, look for the <Bug className="inline" size={16} /> icon in the bottom toolbar
|
||||
2. Click the diagnostics button
|
||||
3. Review the information in the modal about what data will be collected
|
||||
4. Click `Download` to generate and save the diagnostics bundle
|
||||
5. The ZIP file will be saved as `diagnostics_{session_id}.zip`
|
||||
4. Click `Download` to generate and save the diagnostics report
|
||||
5. The JSON file will be saved as `diagnostics_{session_id}.json`
|
||||
|
||||
You can use `scripts/diagnostics-viewer.py` to inspect downloaded diagnostics reports; by default it looks in `~/Downloads`.
|
||||
|
||||
:::tip
|
||||
The diagnostics button is only available when you have an active session, as it needs a session ID to generate the bundle.
|
||||
@@ -45,7 +47,7 @@ The diagnostics feature creates a comprehensive troubleshooting bundle that incl
|
||||
goose session diagnostics
|
||||
|
||||
# Save to a custom location
|
||||
goose session diagnostics --session-id <session_id> --output /path/to/diagnostics.zip
|
||||
goose session diagnostics --session-id <session_id> --output /path/to/diagnostics.json
|
||||
```
|
||||
|
||||
To find your session ID, first list available sessions:
|
||||
@@ -65,17 +67,18 @@ The diagnostics feature creates a comprehensive troubleshooting bundle that incl
|
||||
|
||||
### Using Diagnostics Data
|
||||
|
||||
The diagnostics ZIP file contains several folders:
|
||||
The diagnostics JSON file contains structured sections:
|
||||
|
||||
```
|
||||
diagnostics_abc123def.zip
|
||||
├── logs/
|
||||
│ ├── goose-2024-01-15.jsonl
|
||||
│ ├── goose-2024-01-14.jsonl
|
||||
│ └── ...
|
||||
├── session.json # Your session messages
|
||||
├── config.yaml # Configuration files (if they exist)
|
||||
└── system.txt # System information
|
||||
```json
|
||||
{
|
||||
"system": {},
|
||||
"session": {},
|
||||
"config": {},
|
||||
"logs": {},
|
||||
"prompts": [],
|
||||
"schedule": {},
|
||||
"errors": []
|
||||
}
|
||||
```
|
||||
|
||||
**When to generate diagnostics:**
|
||||
@@ -154,4 +157,3 @@ For issues not resolved by diagnostics:
|
||||
|
||||
- **[Session and System Logs](/docs/guides/logs)**: View detailed logs for debugging individual sessions
|
||||
- **[Telemetry Export](/docs/guides/environment-variables#observability)**: Configure telemetry for performance analysis and production monitoring
|
||||
|
||||
|
||||
+108
-24
@@ -6,9 +6,9 @@
|
||||
WARNING: entirely vibe coded. use as a throwaway tool
|
||||
|
||||
|
||||
Diagnostics Viewer - Browse and inspect Goose diagnostics bundles.
|
||||
Diagnostics Viewer - Browse and inspect Goose diagnostics reports.
|
||||
|
||||
Scans for diagnostics zip files, displays their sessions, and provides
|
||||
Scans for diagnostics JSON reports and legacy zip files, displays their sessions, and provides
|
||||
an interactive viewer for examining session data, logs, and other files.
|
||||
"""
|
||||
import json
|
||||
@@ -188,34 +188,114 @@ class SearchOverlay(Container):
|
||||
|
||||
|
||||
class DiagnosticsSession:
|
||||
"""Represents a diagnostics bundle."""
|
||||
"""Represents a diagnostics report or legacy diagnostics bundle."""
|
||||
|
||||
def __init__(self, zip_path: Path):
|
||||
self.zip_path = zip_path
|
||||
def __init__(self, path: Path):
|
||||
self.path = path
|
||||
self.is_zip = path.suffix == ".zip"
|
||||
self.name = "Unknown Session"
|
||||
self.session_id = zip_path.stem
|
||||
self.created_at = zip_path.stat().st_mtime
|
||||
self.session_id = path.stem
|
||||
self.created_at = path.stat().st_mtime
|
||||
self.report = None
|
||||
self._load_session_name()
|
||||
|
||||
def _load_session_name(self):
|
||||
"""Extract session name from session.json."""
|
||||
"""Extract session name from the report."""
|
||||
if not self.is_zip:
|
||||
self._load_json_report()
|
||||
session = (self.report or {}).get("session") or {}
|
||||
self.name = session.get("name", "Unknown Session")
|
||||
self.session_id = session.get("id", self.path.stem)
|
||||
return
|
||||
|
||||
try:
|
||||
with zipfile.ZipFile(self.zip_path, 'r') as zf:
|
||||
with zipfile.ZipFile(self.path, 'r') as zf:
|
||||
# Find session.json
|
||||
for name in zf.namelist():
|
||||
if name.endswith('session.json'):
|
||||
with zf.open(name) as f:
|
||||
data = json.load(f)
|
||||
self.name = data.get('name', 'Unknown Session')
|
||||
self.session_id = data.get('id', self.zip_path.stem)
|
||||
self.session_id = data.get('id', self.path.stem)
|
||||
break
|
||||
except Exception as e:
|
||||
self.name = f"Error loading: {e}"
|
||||
|
||||
def get_file_list(self) -> list[str]:
|
||||
"""Get list of files in the zip, sorted with system.txt first."""
|
||||
def _load_json_report(self):
|
||||
if self.report is not None:
|
||||
return
|
||||
|
||||
try:
|
||||
with zipfile.ZipFile(self.zip_path, 'r') as zf:
|
||||
self.report = json.loads(self.path.read_text())
|
||||
except Exception as e:
|
||||
self.report = {"error": f"Error loading: {e}"}
|
||||
|
||||
def _json_virtual_files(self) -> dict[str, str]:
|
||||
self._load_json_report()
|
||||
report = self.report or {}
|
||||
files = {
|
||||
"diagnostics.json": json.dumps(report, indent=2),
|
||||
}
|
||||
|
||||
for key, filename in [
|
||||
("system", "system.json"),
|
||||
("config", "config.json"),
|
||||
("extensions", "extensions.json"),
|
||||
("session", "session.json"),
|
||||
("schedule", "schedule.json"),
|
||||
("errors", "errors.json"),
|
||||
]:
|
||||
value = report.get(key)
|
||||
if value is not None:
|
||||
files[filename] = json.dumps(value, indent=2)
|
||||
|
||||
logs = report.get("logs") or {}
|
||||
server = logs.get("server")
|
||||
if isinstance(server, dict) and server.get("content") is not None:
|
||||
files["logs/server.txt"] = server["content"]
|
||||
|
||||
llm_logs = logs.get("llm") or []
|
||||
for index, entry in enumerate(llm_logs):
|
||||
if isinstance(entry, dict) and entry.get("content") is not None:
|
||||
path = Path(entry.get("path") or f"llm_request.{index}.jsonl")
|
||||
files[f"logs/{path.name}"] = entry["content"]
|
||||
|
||||
config = report.get("config") or {}
|
||||
if isinstance(config, dict) and config.get("configYaml"):
|
||||
files["config.yaml"] = config["configYaml"]
|
||||
|
||||
for prompt in report.get("prompts") or []:
|
||||
if isinstance(prompt, dict) and prompt.get("name") and prompt.get("content") is not None:
|
||||
files[f"prompts/{prompt['name']}.txt"] = prompt["content"]
|
||||
|
||||
for recipe in report.get("scheduledRecipes") or []:
|
||||
if isinstance(recipe, dict) and recipe.get("path") and recipe.get("content") is not None:
|
||||
path = Path(recipe["path"])
|
||||
files[f"scheduled_recipes/{path.name}"] = recipe["content"]
|
||||
|
||||
return files
|
||||
|
||||
def get_file_list(self) -> list[str]:
|
||||
"""Get list of report files, sorted with system first."""
|
||||
if not self.is_zip:
|
||||
files = list(self._json_virtual_files().keys())
|
||||
|
||||
def sort_key(f):
|
||||
if f == "system.json":
|
||||
return (0, f)
|
||||
elif f == "session.json":
|
||||
return (1, f)
|
||||
elif f == "config.yaml" or f == "config.json":
|
||||
return (2, f)
|
||||
elif f == "diagnostics.json":
|
||||
return (3, f)
|
||||
else:
|
||||
return (4, f)
|
||||
|
||||
return sorted(files, key=sort_key)
|
||||
|
||||
try:
|
||||
with zipfile.ZipFile(self.path, 'r') as zf:
|
||||
files = zf.namelist()
|
||||
|
||||
# Sort: system.txt first, then session.json, then alphabetically
|
||||
@@ -234,13 +314,16 @@ class DiagnosticsSession:
|
||||
return []
|
||||
|
||||
def read_file(self, filename: str) -> Optional[str]:
|
||||
"""Read a file from the zip.
|
||||
"""Read a file from the report.
|
||||
|
||||
Returns:
|
||||
File content as string, or None if file cannot be read.
|
||||
"""
|
||||
if not self.is_zip:
|
||||
return self._json_virtual_files().get(filename)
|
||||
|
||||
try:
|
||||
with zipfile.ZipFile(self.zip_path, 'r') as zf:
|
||||
with zipfile.ZipFile(self.path, 'r') as zf:
|
||||
with zf.open(filename) as f:
|
||||
return f.read().decode('utf-8', errors='replace')
|
||||
except Exception:
|
||||
@@ -591,8 +674,8 @@ class SessionList(Vertical):
|
||||
list_view = self.query_one(ListView)
|
||||
for session in self.sessions:
|
||||
item = ListItem(
|
||||
Label(f"{session.name}\n[dim]{session.zip_path.name}[/dim]"),
|
||||
name=session.zip_path.name
|
||||
Label(f"{session.name}\n[dim]{session.path.name}[/dim]"),
|
||||
name=session.path.name
|
||||
)
|
||||
list_view.append(item)
|
||||
|
||||
@@ -752,13 +835,14 @@ class DiagnosticsApp(App):
|
||||
self.show_session_list()
|
||||
|
||||
def scan_diagnostics(self):
|
||||
"""Scan for diagnostics zip files."""
|
||||
"""Scan for diagnostics JSON reports and legacy zip files."""
|
||||
self.sessions = []
|
||||
|
||||
# Find all diagnostics zip files
|
||||
for zip_path in self.diagnostics_dir.glob("diagnostics*.zip"):
|
||||
session = DiagnosticsSession(zip_path)
|
||||
self.sessions.append(session)
|
||||
for path in [
|
||||
*self.diagnostics_dir.glob("diagnostics*.json"),
|
||||
*self.diagnostics_dir.glob("diagnostics*.zip"),
|
||||
]:
|
||||
self.sessions.append(DiagnosticsSession(path))
|
||||
|
||||
# Sort by creation time (newest first)
|
||||
self.sessions.sort(key=lambda s: s.created_at, reverse=True)
|
||||
@@ -781,9 +865,9 @@ class DiagnosticsApp(App):
|
||||
|
||||
def on_list_view_selected(self, event: ListView.Selected):
|
||||
"""Handle session selection."""
|
||||
# Find the session by zip name
|
||||
# Find the session by diagnostics file name
|
||||
session_name = event.item.name
|
||||
session = next((s for s in self.sessions if s.zip_path.name == session_name), None)
|
||||
session = next((s for s in self.sessions if s.path.name == session_name), None)
|
||||
if session:
|
||||
self.show_session_viewer(session)
|
||||
|
||||
|
||||
+210
-4
@@ -1730,6 +1730,19 @@
|
||||
],
|
||||
"operationId": "diagnostics",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "level",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/DiagnosticsLevel"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "session_id",
|
||||
"in": "path",
|
||||
@@ -1741,12 +1754,11 @@
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Diagnostics zip file",
|
||||
"description": "Diagnostics report",
|
||||
"content": {
|
||||
"application/zip": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "binary"
|
||||
"$ref": "#/components/schemas/DiagnosticsReport"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4617,6 +4629,200 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"DiagnosticsConfig": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"configPath",
|
||||
"truncated"
|
||||
],
|
||||
"properties": {
|
||||
"configPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"configYaml": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"truncated": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DiagnosticsError": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"message"
|
||||
],
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"DiagnosticsExtensions": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"enabled"
|
||||
],
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"DiagnosticsLevel": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"summary",
|
||||
"full"
|
||||
]
|
||||
},
|
||||
"DiagnosticsLogs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"llm"
|
||||
],
|
||||
"properties": {
|
||||
"llm": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/DiagnosticsTextFile"
|
||||
}
|
||||
},
|
||||
"server": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/DiagnosticsTextFile"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"DiagnosticsPrompt": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"content"
|
||||
],
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DiagnosticsReport": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"schemaVersion",
|
||||
"generatedAt",
|
||||
"level",
|
||||
"system",
|
||||
"extensions",
|
||||
"logs",
|
||||
"prompts",
|
||||
"scheduledRecipes",
|
||||
"errors"
|
||||
],
|
||||
"properties": {
|
||||
"config": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/DiagnosticsConfig"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"errors": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/DiagnosticsError"
|
||||
}
|
||||
},
|
||||
"extensions": {
|
||||
"$ref": "#/components/schemas/DiagnosticsExtensions"
|
||||
},
|
||||
"generatedAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"level": {
|
||||
"$ref": "#/components/schemas/DiagnosticsLevel"
|
||||
},
|
||||
"logs": {
|
||||
"$ref": "#/components/schemas/DiagnosticsLogs"
|
||||
},
|
||||
"prompts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/DiagnosticsPrompt"
|
||||
}
|
||||
},
|
||||
"schedule": {
|
||||
"nullable": true
|
||||
},
|
||||
"scheduledRecipes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/DiagnosticsScheduledRecipe"
|
||||
}
|
||||
},
|
||||
"schemaVersion": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"minimum": 0
|
||||
},
|
||||
"session": {
|
||||
"nullable": true
|
||||
},
|
||||
"system": {
|
||||
"$ref": "#/components/schemas/SystemInfo"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DiagnosticsScheduledRecipe": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"path",
|
||||
"content"
|
||||
],
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DiagnosticsTextFile": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"path",
|
||||
"content",
|
||||
"truncated"
|
||||
],
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"truncated": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DictationProvider": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { getAcpClient } from './acpConnection';
|
||||
import type { DiagnosticsReport } from '../api';
|
||||
|
||||
export type DiagnosticsLevel = 'summary' | 'full';
|
||||
|
||||
export async function getDiagnosticsReport(
|
||||
sessionId: string,
|
||||
level: DiagnosticsLevel
|
||||
): Promise<DiagnosticsReport> {
|
||||
const client = await getAcpClient();
|
||||
const response = await client.goose.diagnosticsGet_unstable({
|
||||
sessionId,
|
||||
level,
|
||||
});
|
||||
return response.report as DiagnosticsReport;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -247,6 +247,59 @@ export type DeleteRecipeRequest = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type DiagnosticsConfig = {
|
||||
configPath: string;
|
||||
configYaml?: string | null;
|
||||
truncated: boolean;
|
||||
};
|
||||
|
||||
export type DiagnosticsError = {
|
||||
message: string;
|
||||
path?: string | null;
|
||||
};
|
||||
|
||||
export type DiagnosticsExtensions = {
|
||||
enabled: Array<string>;
|
||||
};
|
||||
|
||||
export type DiagnosticsLevel = 'summary' | 'full';
|
||||
|
||||
export type DiagnosticsLogs = {
|
||||
llm: Array<DiagnosticsTextFile>;
|
||||
server?: DiagnosticsTextFile | null;
|
||||
};
|
||||
|
||||
export type DiagnosticsPrompt = {
|
||||
content: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type DiagnosticsReport = {
|
||||
config?: DiagnosticsConfig | null;
|
||||
errors: Array<DiagnosticsError>;
|
||||
extensions: DiagnosticsExtensions;
|
||||
generatedAt: string;
|
||||
level: DiagnosticsLevel;
|
||||
logs: DiagnosticsLogs;
|
||||
prompts: Array<DiagnosticsPrompt>;
|
||||
schedule?: unknown;
|
||||
scheduledRecipes: Array<DiagnosticsScheduledRecipe>;
|
||||
schemaVersion: number;
|
||||
session?: unknown;
|
||||
system: SystemInfo;
|
||||
};
|
||||
|
||||
export type DiagnosticsScheduledRecipe = {
|
||||
content: string;
|
||||
path: string;
|
||||
};
|
||||
|
||||
export type DiagnosticsTextFile = {
|
||||
content: string;
|
||||
path: string;
|
||||
truncated: boolean;
|
||||
};
|
||||
|
||||
export type DictationProvider = 'openai' | 'elevenlabs' | 'groq' | 'local';
|
||||
|
||||
export type DictationProviderStatus = {
|
||||
@@ -3098,7 +3151,9 @@ export type DiagnosticsData = {
|
||||
path: {
|
||||
session_id: string;
|
||||
};
|
||||
query?: never;
|
||||
query?: {
|
||||
level?: DiagnosticsLevel | null;
|
||||
};
|
||||
url: '/diagnostics/{session_id}';
|
||||
};
|
||||
|
||||
@@ -3111,9 +3166,9 @@ export type DiagnosticsErrors = {
|
||||
|
||||
export type DiagnosticsResponses = {
|
||||
/**
|
||||
* Diagnostics zip file
|
||||
* Diagnostics report
|
||||
*/
|
||||
200: Blob | File;
|
||||
200: DiagnosticsReport;
|
||||
};
|
||||
|
||||
export type DiagnosticsResponse = DiagnosticsResponses[keyof DiagnosticsResponses];
|
||||
|
||||
@@ -2,8 +2,8 @@ import React, { useState } from 'react';
|
||||
import { AlertTriangle, Download, Github } from 'lucide-react';
|
||||
import { Button } from './button';
|
||||
import { toastError } from '../../toasts';
|
||||
import { diagnostics, systemInfo } from '../../api';
|
||||
import { defineMessages, useIntl } from '../../i18n';
|
||||
import { getDiagnosticsReport } from '../../acp/diagnostics';
|
||||
|
||||
const i18n = defineMessages({
|
||||
reportProblem: {
|
||||
@@ -13,7 +13,7 @@ const i18n = defineMessages({
|
||||
description: {
|
||||
id: 'diagnosticsModal.description',
|
||||
defaultMessage:
|
||||
'You can download a diagnostics zip file to share with the team, or file a bug directly on GitHub with your system details pre-filled. A diagnostics report contains the following:',
|
||||
'You can download a diagnostics JSON report to share with the team, or file a bug directly on GitHub with your system details pre-filled. A diagnostics report contains the following:',
|
||||
},
|
||||
systemInfo: {
|
||||
id: 'diagnosticsModal.systemInfo',
|
||||
@@ -66,7 +66,7 @@ const i18n = defineMessages({
|
||||
},
|
||||
diagnosticsErrorMsg: {
|
||||
id: 'diagnosticsModal.diagnosticsErrorMsg',
|
||||
defaultMessage: 'Failed to download diagnostics',
|
||||
defaultMessage: 'Failed to download diagnostics report',
|
||||
},
|
||||
systemInfoErrorTitle: {
|
||||
id: 'diagnosticsModal.systemInfoErrorTitle',
|
||||
@@ -97,16 +97,14 @@ export const DiagnosticsModal: React.FC<DiagnosticsModalProps> = ({
|
||||
setIsDownloading(true);
|
||||
|
||||
try {
|
||||
const response = await diagnostics({
|
||||
path: { session_id: sessionId },
|
||||
throwOnError: true,
|
||||
const report = await getDiagnosticsReport(sessionId, 'full');
|
||||
const blob = new Blob([`${JSON.stringify(report, null, 2)}\n`], {
|
||||
type: 'application/json',
|
||||
});
|
||||
|
||||
const blob = new Blob([response.data], { type: 'application/zip' });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `diagnostics_${sessionId}.zip`;
|
||||
a.download = `diagnostics_${sessionId}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
@@ -127,12 +125,12 @@ export const DiagnosticsModal: React.FC<DiagnosticsModalProps> = ({
|
||||
setIsFilingBug(true);
|
||||
|
||||
try {
|
||||
const response = await systemInfo({ throwOnError: true });
|
||||
const info = response.data;
|
||||
const report = await getDiagnosticsReport(sessionId, 'summary');
|
||||
const info = report.system;
|
||||
|
||||
const providerModel =
|
||||
info.provider && info.model
|
||||
? `${info.provider} – ${info.model}`
|
||||
? `${info.provider} - ${info.model}`
|
||||
: info.provider || info.model || '[e.g. Google – gemini-1.5-pro]';
|
||||
|
||||
const extensions =
|
||||
@@ -145,7 +143,7 @@ export const DiagnosticsModal: React.FC<DiagnosticsModalProps> = ({
|
||||
💡 Before filing, please check common issues:
|
||||
https://goose-docs.ai/docs/troubleshooting
|
||||
|
||||
📦 To help us debug faster, attach your **diagnostics zip** if possible.
|
||||
📦 To help us debug faster, attach your **diagnostics JSON report** if possible.
|
||||
👉 How to capture it: https://goose-docs.ai/docs/troubleshooting/diagnostics-and-reporting/
|
||||
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
@@ -804,10 +804,10 @@
|
||||
"defaultMessage": "Configuration settings"
|
||||
},
|
||||
"diagnosticsModal.description": {
|
||||
"defaultMessage": "You can download a diagnostics zip file to share with the team, or file a bug directly on GitHub with your system details pre-filled. A diagnostics report contains the following:"
|
||||
"defaultMessage": "You can download a diagnostics JSON report to share with the team, or file a bug directly on GitHub with your system details pre-filled. A diagnostics report contains the following:"
|
||||
},
|
||||
"diagnosticsModal.diagnosticsErrorMsg": {
|
||||
"defaultMessage": "Failed to download diagnostics"
|
||||
"defaultMessage": "Failed to download diagnostics report"
|
||||
},
|
||||
"diagnosticsModal.diagnosticsErrorTitle": {
|
||||
"defaultMessage": "Diagnostics Error"
|
||||
|
||||
@@ -30,6 +30,8 @@ import type {
|
||||
DeleteRecipeRequest_unstable,
|
||||
DeleteSessionRequest,
|
||||
DeleteSourceRequest_unstable,
|
||||
DiagnosticsGetRequest_unstable,
|
||||
DiagnosticsGetResponse_unstable,
|
||||
DictationConfigRequest_unstable,
|
||||
DictationConfigResponse_unstable,
|
||||
DictationModelCancelRequest_unstable,
|
||||
@@ -135,6 +137,7 @@ import {
|
||||
zCustomProviderUpdateResponse_unstable,
|
||||
zDecodeRecipeResponse_unstable,
|
||||
zDefaultsReadResponse_unstable,
|
||||
zDiagnosticsGetResponse_unstable,
|
||||
zDictationConfigResponse_unstable,
|
||||
zDictationModelDownloadProgressResponse_unstable,
|
||||
zDictationModelsListResponse_unstable,
|
||||
@@ -251,6 +254,18 @@ export class GooseExtClient {
|
||||
) as SteerSessionResponse_unstable;
|
||||
}
|
||||
|
||||
async diagnosticsGet_unstable(
|
||||
params: DiagnosticsGetRequest_unstable,
|
||||
): Promise<DiagnosticsGetResponse_unstable> {
|
||||
const raw = await this.conn.extMethod(
|
||||
"_goose/unstable/diagnostics/get",
|
||||
params,
|
||||
);
|
||||
return zDiagnosticsGetResponse_unstable.parse(
|
||||
raw,
|
||||
) as DiagnosticsGetResponse_unstable;
|
||||
}
|
||||
|
||||
async sessionDelete(params: DeleteSessionRequest): Promise<void> {
|
||||
await this.conn.extMethod("session/delete", params);
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -489,6 +489,17 @@ export type SteerSessionResponse_unstable = {
|
||||
messageId: string;
|
||||
};
|
||||
|
||||
export type DiagnosticsGetRequest_unstable = {
|
||||
sessionId: string;
|
||||
level?: DiagnosticsReportLevel;
|
||||
};
|
||||
|
||||
export type DiagnosticsReportLevel = 'summary' | 'full';
|
||||
|
||||
export type DiagnosticsGetResponse_unstable = {
|
||||
report: unknown;
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a session.
|
||||
*/
|
||||
@@ -1806,14 +1817,14 @@ export type RecipeParamsAction = 'submit' | 'cancel';
|
||||
export type ExtRequest = {
|
||||
id: string;
|
||||
method: string;
|
||||
params?: AddSessionExtensionRequest_unstable | RemoveSessionExtensionRequest_unstable | GetToolsRequest_unstable | GooseToolCallRequest_unstable | ReadResourceRequest_unstable | UpdateWorkingDirRequest_unstable | SetSessionSystemPromptRequest_unstable | SteerSessionRequest_unstable | DeleteSessionRequest | GetConfigExtensionsRequest_unstable | GetAvailableExtensionsRequest_unstable | AddConfigExtensionRequest_unstable | RemoveConfigExtensionRequest_unstable | SetConfigExtensionEnabledRequest_unstable | GetSessionExtensionsRequest_unstable | ListProvidersRequest_unstable | ProviderSupportedModelsListRequest_unstable | ProviderCatalogListRequest_unstable | ProviderSetupCatalogListRequest_unstable | ProviderCatalogTemplateRequest_unstable | CustomProviderCreateRequest_unstable | CustomProviderReadRequest_unstable | CustomProviderUpdateRequest_unstable | CustomProviderDeleteRequest_unstable | RefreshProviderInventoryRequest_unstable | ProviderConfigReadRequest_unstable | ProviderConfigStatusRequest_unstable | ProviderConfigSaveRequest_unstable | ProviderConfigDeleteRequest_unstable | ProviderConfigAuthenticateRequest_unstable | PreferencesReadRequest_unstable | PreferencesSaveRequest_unstable | PreferencesRemoveRequest_unstable | DefaultsReadRequest_unstable | DefaultsSaveRequest_unstable | OnboardingImportScanRequest_unstable | OnboardingImportApplyRequest_unstable | ExportSessionRequest_unstable | ImportSessionRequest_unstable | EncodeRecipeRequest_unstable | DecodeRecipeRequest_unstable | ScanRecipeRequest_unstable | ListRecipesRequest_unstable | DeleteRecipeRequest_unstable | ScheduleRecipeRequest_unstable | SetRecipeSlashCommandRequest_unstable | SaveRecipeRequest_unstable | ParseRecipeRequest_unstable | RecipeToYamlRequest_unstable | GetSessionInfoRequest_unstable | TruncateSessionConversationRequest_unstable | UpdateSessionProjectRequest_unstable | RenameSessionRequest_unstable | ArchiveSessionRequest_unstable | UnarchiveSessionRequest_unstable | CreateSourceRequest_unstable | ListSourcesRequest_unstable | UpdateSourceRequest_unstable | DeleteSourceRequest_unstable | ExportSourceRequest_unstable | ImportSourcesRequest_unstable | DictationTranscribeRequest_unstable | DictationConfigRequest_unstable | DictationSecretSaveRequest_unstable | DictationSecretDeleteRequest_unstable | DictationModelsListRequest_unstable | DictationModelDownloadRequest_unstable | DictationModelDownloadProgressRequest_unstable | DictationModelCancelRequest_unstable | DictationModelDeleteRequest_unstable | DictationModelSelectRequest_unstable | {
|
||||
params?: AddSessionExtensionRequest_unstable | RemoveSessionExtensionRequest_unstable | GetToolsRequest_unstable | GooseToolCallRequest_unstable | ReadResourceRequest_unstable | UpdateWorkingDirRequest_unstable | SetSessionSystemPromptRequest_unstable | SteerSessionRequest_unstable | DiagnosticsGetRequest_unstable | DeleteSessionRequest | GetConfigExtensionsRequest_unstable | GetAvailableExtensionsRequest_unstable | AddConfigExtensionRequest_unstable | RemoveConfigExtensionRequest_unstable | SetConfigExtensionEnabledRequest_unstable | GetSessionExtensionsRequest_unstable | ListProvidersRequest_unstable | ProviderSupportedModelsListRequest_unstable | ProviderCatalogListRequest_unstable | ProviderSetupCatalogListRequest_unstable | ProviderCatalogTemplateRequest_unstable | CustomProviderCreateRequest_unstable | CustomProviderReadRequest_unstable | CustomProviderUpdateRequest_unstable | CustomProviderDeleteRequest_unstable | RefreshProviderInventoryRequest_unstable | ProviderConfigReadRequest_unstable | ProviderConfigStatusRequest_unstable | ProviderConfigSaveRequest_unstable | ProviderConfigDeleteRequest_unstable | ProviderConfigAuthenticateRequest_unstable | PreferencesReadRequest_unstable | PreferencesSaveRequest_unstable | PreferencesRemoveRequest_unstable | DefaultsReadRequest_unstable | DefaultsSaveRequest_unstable | OnboardingImportScanRequest_unstable | OnboardingImportApplyRequest_unstable | ExportSessionRequest_unstable | ImportSessionRequest_unstable | EncodeRecipeRequest_unstable | DecodeRecipeRequest_unstable | ScanRecipeRequest_unstable | ListRecipesRequest_unstable | DeleteRecipeRequest_unstable | ScheduleRecipeRequest_unstable | SetRecipeSlashCommandRequest_unstable | SaveRecipeRequest_unstable | ParseRecipeRequest_unstable | RecipeToYamlRequest_unstable | GetSessionInfoRequest_unstable | TruncateSessionConversationRequest_unstable | UpdateSessionProjectRequest_unstable | RenameSessionRequest_unstable | ArchiveSessionRequest_unstable | UnarchiveSessionRequest_unstable | CreateSourceRequest_unstable | ListSourcesRequest_unstable | UpdateSourceRequest_unstable | DeleteSourceRequest_unstable | ExportSourceRequest_unstable | ImportSourcesRequest_unstable | DictationTranscribeRequest_unstable | DictationConfigRequest_unstable | DictationSecretSaveRequest_unstable | DictationSecretDeleteRequest_unstable | DictationModelsListRequest_unstable | DictationModelDownloadRequest_unstable | DictationModelDownloadProgressRequest_unstable | DictationModelCancelRequest_unstable | DictationModelDeleteRequest_unstable | DictationModelSelectRequest_unstable | {
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type ExtResponse = {
|
||||
id: string;
|
||||
result?: EmptyResponse | GetToolsResponse_unstable | GooseToolCallResponse_unstable | ReadResourceResponse_unstable | SteerSessionResponse_unstable | GetConfigExtensionsResponse_unstable | GetAvailableExtensionsResponse_unstable | GetSessionExtensionsResponse_unstable | ListProvidersResponse_unstable | ProviderSupportedModelsListResponse_unstable | ProviderCatalogListResponse_unstable | ProviderSetupCatalogListResponse_unstable | ProviderCatalogTemplateResponse_unstable | CustomProviderCreateResponse_unstable | CustomProviderReadResponse_unstable | CustomProviderUpdateResponse_unstable | CustomProviderDeleteResponse_unstable | RefreshProviderInventoryResponse_unstable | ProviderConfigReadResponse_unstable | ProviderConfigStatusResponse_unstable | ProviderConfigChangeResponse_unstable | PreferencesReadResponse_unstable | DefaultsReadResponse_unstable | OnboardingImportScanResponse_unstable | OnboardingImportApplyResponse_unstable | ExportSessionResponse_unstable | ImportSessionResponse_unstable | EncodeRecipeResponse_unstable | DecodeRecipeResponse_unstable | ScanRecipeResponse_unstable | ListRecipesResponse_unstable | SaveRecipeResponse_unstable | ParseRecipeResponse_unstable | RecipeToYamlResponse_unstable | GetSessionInfoResponse_unstable | CreateSourceResponse_unstable | ListSourcesResponse_unstable | UpdateSourceResponse_unstable | ExportSourceResponse_unstable | ImportSourcesResponse_unstable | DictationTranscribeResponse_unstable | DictationConfigResponse_unstable | DictationModelsListResponse_unstable | DictationModelDownloadProgressResponse_unstable | unknown;
|
||||
result?: EmptyResponse | GetToolsResponse_unstable | GooseToolCallResponse_unstable | ReadResourceResponse_unstable | SteerSessionResponse_unstable | DiagnosticsGetResponse_unstable | GetConfigExtensionsResponse_unstable | GetAvailableExtensionsResponse_unstable | GetSessionExtensionsResponse_unstable | ListProvidersResponse_unstable | ProviderSupportedModelsListResponse_unstable | ProviderCatalogListResponse_unstable | ProviderSetupCatalogListResponse_unstable | ProviderCatalogTemplateResponse_unstable | CustomProviderCreateResponse_unstable | CustomProviderReadResponse_unstable | CustomProviderUpdateResponse_unstable | CustomProviderDeleteResponse_unstable | RefreshProviderInventoryResponse_unstable | ProviderConfigReadResponse_unstable | ProviderConfigStatusResponse_unstable | ProviderConfigChangeResponse_unstable | PreferencesReadResponse_unstable | DefaultsReadResponse_unstable | OnboardingImportScanResponse_unstable | OnboardingImportApplyResponse_unstable | ExportSessionResponse_unstable | ImportSessionResponse_unstable | EncodeRecipeResponse_unstable | DecodeRecipeResponse_unstable | ScanRecipeResponse_unstable | ListRecipesResponse_unstable | SaveRecipeResponse_unstable | ParseRecipeResponse_unstable | RecipeToYamlResponse_unstable | GetSessionInfoResponse_unstable | CreateSourceResponse_unstable | ListSourcesResponse_unstable | UpdateSourceResponse_unstable | ExportSourceResponse_unstable | ImportSourcesResponse_unstable | DictationTranscribeResponse_unstable | DictationConfigResponse_unstable | DictationModelsListResponse_unstable | DictationModelDownloadProgressResponse_unstable | unknown;
|
||||
} | {
|
||||
error: {
|
||||
code: number;
|
||||
|
||||
@@ -458,6 +458,17 @@ export const zSteerSessionResponse_unstable = z.object({
|
||||
messageId: z.string()
|
||||
});
|
||||
|
||||
export const zDiagnosticsReportLevel = z.enum(['summary', 'full']);
|
||||
|
||||
export const zDiagnosticsGetRequest_unstable = z.object({
|
||||
sessionId: z.string(),
|
||||
level: zDiagnosticsReportLevel.optional().default('summary')
|
||||
});
|
||||
|
||||
export const zDiagnosticsGetResponse_unstable = z.object({
|
||||
report: z.unknown()
|
||||
});
|
||||
|
||||
/**
|
||||
* Delete a session.
|
||||
*/
|
||||
@@ -1921,6 +1932,7 @@ export const zExtRequest = z.object({
|
||||
zUpdateWorkingDirRequest_unstable,
|
||||
zSetSessionSystemPromptRequest_unstable,
|
||||
zSteerSessionRequest_unstable,
|
||||
zDiagnosticsGetRequest_unstable,
|
||||
zDeleteSessionRequest,
|
||||
zGetConfigExtensionsRequest_unstable,
|
||||
zGetAvailableExtensionsRequest_unstable,
|
||||
@@ -2002,6 +2014,7 @@ export const zExtResponse = z.union([
|
||||
zGooseToolCallResponse_unstable,
|
||||
zReadResourceResponse_unstable,
|
||||
zSteerSessionResponse_unstable,
|
||||
zDiagnosticsGetResponse_unstable,
|
||||
zGetConfigExtensionsResponse_unstable,
|
||||
zGetAvailableExtensionsResponse_unstable,
|
||||
zGetSessionExtensionsResponse_unstable,
|
||||
|
||||
Reference in New Issue
Block a user