feat(desktop): add markdown format option to session export (#10703)

Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
This commit is contained in:
Harnoor Singh
2026-08-05 01:34:24 +05:30
committed by GitHub
parent e7088ee791
commit 06f218cd14
29 changed files with 1230 additions and 932 deletions
+2 -107
View File
@@ -1,4 +1,3 @@
use crate::session::user_projected_message_to_markdown;
use anyhow::{Context, Result};
use cliclack::{confirm, multiselect, select};
@@ -8,7 +7,8 @@ use goose::config::Config;
#[cfg(feature = "nostr")]
use goose::session::nostr_share;
use goose::session::{
generate_diagnostics, DiagnosticsLevel, Session, SessionManager, SessionType,
export_session_to_markdown, generate_diagnostics, DiagnosticsLevel, Session, SessionManager,
SessionType,
};
use goose::utils::safe_truncate;
use regex::Regex;
@@ -365,74 +365,6 @@ pub async fn handle_diagnostics(session_id: &str, output_path: Option<PathBuf>)
Ok(())
}
fn export_session_to_markdown(
messages: Vec<goose::conversation::message::Message>,
session_name: &String,
) -> String {
let mut markdown_output = String::new();
markdown_output.push_str(&format!("# Session Export: {}\n\n", session_name));
if messages.is_empty() {
markdown_output.push_str("*(This session has no messages)*\n");
return markdown_output;
}
markdown_output.push_str(&format!("*Total messages: {}*\n\n---\n\n", messages.len()));
// Track if the last message had tool requests to properly handle tool responses
let mut skip_next_if_tool_response = false;
for message in &messages {
// Check if this is a User message containing only ToolResponses
let is_only_tool_response = message.role == rmcp::model::Role::User
&& message.content.iter().all(|content| {
matches!(
content,
goose::conversation::message::MessageContent::ToolResponse(_)
)
});
// If the previous message had tool requests and this one is just tool responses,
// don't create a new User section - we'll attach the responses to the tool calls
if skip_next_if_tool_response && is_only_tool_response {
// Export the tool responses without a User heading
markdown_output.push_str(&user_projected_message_to_markdown(message));
markdown_output.push_str("\n\n---\n\n");
skip_next_if_tool_response = false;
continue;
}
// Reset the skip flag - we'll update it below if needed
skip_next_if_tool_response = false;
// Output the role prefix except for tool response-only messages
if !is_only_tool_response {
let role_prefix = match message.role {
rmcp::model::Role::User => "### User:\n",
rmcp::model::Role::Assistant => "### Assistant:\n",
};
markdown_output.push_str(role_prefix);
}
// Add the message content
markdown_output.push_str(&user_projected_message_to_markdown(message));
markdown_output.push_str("\n\n---\n\n");
// Check if this message has any tool requests, to handle the next message differently
if message.content.iter().any(|content| {
matches!(
content,
goose::conversation::message::MessageContent::ToolRequest(_)
)
}) {
skip_next_if_tool_response = true;
}
}
markdown_output
}
/// Prompt the user to interactively select a session
///
/// Shows a list of available sessions and lets the user select one
@@ -487,40 +419,3 @@ pub async fn prompt_interactive_session_selection(
Err(anyhow::anyhow!("Invalid selection"))
}
}
#[cfg(test)]
mod tests {
use super::*;
use goose::conversation::message::Message;
use goose::conversation::Conversation;
use rmcp::model::{Annotations, ContentBlock, Role, TextContent};
#[test]
fn markdown_export_preserves_user_audience_tool_output() {
let user_output = ContentBlock::Text(
TextContent::new("user-visible output")
.with_annotations(Annotations::default().with_audience(vec![Role::User])),
);
let assistant_output = ContentBlock::Text(
TextContent::new("assistant-only output")
.with_annotations(Annotations::default().with_audience(vec![Role::Assistant])),
);
let conversation = Conversation::new_unvalidated([Message::user().with_tool_response(
"tool-1",
Ok(rmcp::model::CallToolResult::success(vec![
user_output,
assistant_output,
ContentBlock::text("shared output"),
])),
)]);
let markdown = export_session_to_markdown(
conversation.user_visible_messages(),
&"Audience export".to_string(),
);
assert!(markdown.contains("user-visible output"));
assert!(markdown.contains("shared output"));
assert!(!markdown.contains("assistant-only output"));
}
}
-2
View File
@@ -2,7 +2,6 @@ mod builder;
mod completion;
pub mod editor;
mod elicitation;
mod export;
mod input;
mod output;
mod paste;
@@ -20,7 +19,6 @@ use std::str::FromStr;
use tokio::signal::ctrl_c;
use tokio_util::task::AbortOnDropHandle;
pub use self::export::{message_to_markdown, user_projected_message_to_markdown};
pub use builder::{build_session, SessionBuilderConfig};
use console::Color;
use goose::agents::platform_extensions::developer::shell::{
+33 -2
View File
@@ -758,15 +758,26 @@ pub struct UnarchiveSessionRequest {
pub session_id: String,
}
/// Export a session as a JSON string.
/// Export a session as a JSON or markdown string.
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcRequest)]
#[request(method = "_goose/unstable/session/export", response = ExportSessionResponse)]
#[serde(rename_all = "camelCase")]
pub struct ExportSessionRequest {
pub session_id: String,
#[serde(default)]
pub format: SessionExportFormat,
}
/// Export session response — raw JSON of the goose session with `conversation`.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum SessionExportFormat {
#[default]
Json,
Markdown,
}
/// Export session response — raw JSON of the goose session with `conversation`,
/// or a markdown transcript when `format` is `markdown`.
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcResponse)]
pub struct ExportSessionResponse {
pub data: String,
@@ -2262,3 +2273,23 @@ pub struct SetToolPermissionsRequest {
#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema, JsonRpcResponse)]
pub struct SetToolPermissionsResponse {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn export_session_request_defaults_to_json_without_format() {
let req: ExportSessionRequest = serde_json::from_str(r#"{"sessionId":"abc"}"#).unwrap();
assert_eq!(req.format, SessionExportFormat::Json);
}
#[test]
fn export_session_request_accepts_markdown_format() {
let req: ExportSessionRequest =
serde_json::from_str(r#"{"sessionId":"abc","format":"markdown"}"#).unwrap();
assert_eq!(req.format, SessionExportFormat::Markdown);
}
}
+13 -2
View File
@@ -3409,15 +3409,26 @@
"properties": {
"sessionId": {
"type": "string"
},
"format": {
"$ref": "#/$defs/SessionExportFormat",
"default": "json"
}
},
"required": [
"sessionId"
],
"description": "Export a session as a JSON string.",
"description": "Export a session as a JSON or markdown string.",
"x-side": "agent",
"x-method": "_goose/unstable/session/export"
},
"SessionExportFormat": {
"type": "string",
"enum": [
"json",
"markdown"
]
},
"ExportSessionResponse_unstable": {
"type": "object",
"properties": {
@@ -3428,7 +3439,7 @@
"required": [
"data"
],
"description": "Export session response — raw JSON of the goose session with `conversation`.",
"description": "Export session response — raw JSON of the goose session with `conversation`,\nor a markdown transcript when `format` is `markdown`.",
"x-side": "agent",
"x-method": "_goose/unstable/session/export"
},
@@ -115,11 +115,15 @@ impl GooseAcpAgent {
&self,
req: ExportSessionRequest,
) -> Result<ExportSessionResponse, agent_client_protocol::Error> {
let data = self
.session_manager
.export_session(&req.session_id)
.await
.internal_err()?;
let data = match req.format {
SessionExportFormat::Json => self.session_manager.export_session(&req.session_id).await,
SessionExportFormat::Markdown => {
self.session_manager
.export_session_markdown(&req.session_id)
.await
}
}
.internal_err()?;
Ok(ExportSessionResponse { data })
}
@@ -1,7 +1,7 @@
use goose::conversation::message::{
use crate::conversation::message::{
ActionRequiredData, Message, MessageContent, ToolNameParts, ToolRequest, ToolResponse,
};
use goose::utils::safe_truncate;
use crate::utils::safe_truncate;
use rmcp::model::{ContentBlock, ResourceContents, Role};
use serde_json::Value;
@@ -446,11 +446,65 @@ fn message_to_markdown_for_audience(
md.trim_end_matches("\n").to_string()
}
pub fn export_session_to_markdown(messages: Vec<Message>, session_name: &str) -> String {
let mut markdown_output = String::new();
markdown_output.push_str(&format!("# Session Export: {}\n\n", session_name));
if messages.is_empty() {
markdown_output.push_str("*(This session has no messages)*\n");
return markdown_output;
}
markdown_output.push_str(&format!("*Total messages: {}*\n\n---\n\n", messages.len()));
let mut skip_next_if_tool_response = false;
for message in &messages {
let is_only_tool_response = message.role == Role::User
&& message
.content
.iter()
.all(|content| matches!(content, MessageContent::ToolResponse(_)));
if skip_next_if_tool_response && is_only_tool_response {
markdown_output.push_str(&user_projected_message_to_markdown(message));
markdown_output.push_str("\n\n---\n\n");
skip_next_if_tool_response = false;
continue;
}
skip_next_if_tool_response = false;
if !is_only_tool_response {
let role_prefix = match message.role {
Role::User => "### User:\n",
Role::Assistant => "### Assistant:\n",
};
markdown_output.push_str(role_prefix);
}
markdown_output.push_str(&user_projected_message_to_markdown(message));
markdown_output.push_str("\n\n---\n\n");
if message
.content
.iter()
.any(|content| matches!(content, MessageContent::ToolRequest(_)))
{
skip_next_if_tool_response = true;
}
}
markdown_output
}
#[cfg(test)]
mod tests {
use super::*;
use goose::conversation::message::{Message, ToolRequest, ToolResponse};
use rmcp::model::{CallToolRequestParams, ContentBlock, TextContent};
use crate::conversation::message::{Message, ToolRequest, ToolResponse};
use crate::conversation::Conversation;
use rmcp::model::{Annotations, CallToolRequestParams, ContentBlock, TextContent};
use rmcp::object;
use serde_json::json;
@@ -1139,4 +1193,39 @@ found 0 vulnerabilities"#;
assert!(response_result.contains("added 57 packages"));
assert!(response_result.contains("found 0 vulnerabilities"));
}
#[test]
fn markdown_export_preserves_user_audience_tool_output() {
let user_output = ContentBlock::Text(
TextContent::new("user-visible output")
.with_annotations(Annotations::default().with_audience(vec![Role::User])),
);
let assistant_output = ContentBlock::Text(
TextContent::new("assistant-only output")
.with_annotations(Annotations::default().with_audience(vec![Role::Assistant])),
);
let conversation = Conversation::new_unvalidated([Message::user().with_tool_response(
"tool-1",
Ok(rmcp::model::CallToolResult::success(vec![
user_output,
assistant_output,
ContentBlock::text("shared output"),
])),
)]);
let markdown =
export_session_to_markdown(conversation.user_visible_messages(), "Audience export");
assert!(markdown.contains("user-visible output"));
assert!(markdown.contains("shared output"));
assert!(!markdown.contains("assistant-only output"));
}
#[test]
fn markdown_export_handles_empty_conversation() {
let markdown = export_session_to_markdown(Vec::new(), "Empty session");
assert!(markdown.contains("# Session Export: Empty session"));
assert!(markdown.contains("*(This session has no messages)*"));
}
}
+4
View File
@@ -1,5 +1,6 @@
mod chat_history_search;
mod diagnostics;
mod export_markdown;
pub mod extension_data;
pub mod import_formats;
mod last_message_snippet;
@@ -15,6 +16,9 @@ pub use diagnostics::{
DiagnosticsLevel, DiagnosticsLogs, DiagnosticsPrompt, DiagnosticsReport,
DiagnosticsScheduledRecipe, DiagnosticsTextFile, SystemInfo,
};
pub use export_markdown::{
export_session_to_markdown, message_to_markdown, user_projected_message_to_markdown,
};
pub use extension_data::{EnabledExtensionsState, ExtensionData, ExtensionState, TodoState};
pub use session_manager::{
Session, SessionInsights, SessionManager, SessionNameUpdate, SessionType, SessionUpdateBuilder,
@@ -5,6 +5,7 @@ use crate::conversation::Conversation;
use crate::providers::base::CostSource;
use crate::providers::base::Provider;
use crate::recipe::Recipe;
use crate::session::export_markdown::export_session_to_markdown;
use crate::session::extension_data::ExtensionData;
use crate::session::session_naming::{
generate_session_name, MSG_COUNT_FOR_SESSION_NAME_GENERATION,
@@ -489,6 +490,15 @@ impl SessionManager {
self.storage.export_session(id).await
}
pub async fn export_session_markdown(&self, id: &str) -> Result<String> {
let session = self.get_session(id, true).await?;
let messages = session
.conversation
.map(|conversation| conversation.user_visible_messages())
.unwrap_or_default();
Ok(export_session_to_markdown(messages, &session.name))
}
pub async fn import_session(
&self,
json: &str,
+7 -7
View File
@@ -5,7 +5,7 @@ import type {
NewSessionRequest,
SessionInfo,
} from '@agentclientprotocol/sdk';
import type { GooseExtension, SessionImportSource } from '@aaif/goose-sdk';
import type { GooseExtension, SessionExportFormat, SessionImportSource } from '@aaif/goose-sdk';
import { getAcpClient } from './acpConnection';
import type { ExtensionLoadResult } from '../types/extensions';
import type { Session } from '../types/session';
@@ -295,16 +295,16 @@ export async function acpForkSession(
return String(response.sessionId);
}
export async function acpExportSession(sessionId: string): Promise<string> {
export async function acpExportSession(
sessionId: string,
format: SessionExportFormat = 'json'
): Promise<string> {
const client = await getAcpClient();
const response = await client.goose.sessionExport_unstable({ sessionId });
const response = await client.goose.sessionExport_unstable({ sessionId, format });
return response.data;
}
export async function acpImportSession(
input: string,
source: SessionImportSource
): Promise<void> {
export async function acpImportSession(input: string, source: SessionImportSource): Promise<void> {
const client = await getAcpClient();
await client.goose.sessionImport_unstable({ input, source });
}
File diff suppressed because it is too large Load Diff
+9
View File
@@ -3770,6 +3770,12 @@
"sessions.action.export": {
"defaultMessage": "Sitzung exportieren"
},
"sessions.action.exportJson": {
"defaultMessage": "JSON"
},
"sessions.action.exportMarkdown": {
"defaultMessage": "Markdown"
},
"sessions.action.openNewWindow": {
"defaultMessage": "In neuem Fenster öffnen"
},
@@ -3869,6 +3875,9 @@
"sessions.toast.duplicated": {
"defaultMessage": "Sitzung \"{name}\" erfolgreich dupliziert"
},
"sessions.toast.exportFailed": {
"defaultMessage": "Sitzung konnte nicht exportiert werden: {error}"
},
"sessions.toast.exported": {
"defaultMessage": "Sitzung erfolgreich exportiert"
},
+9
View File
@@ -3770,6 +3770,12 @@
"sessions.action.export": {
"defaultMessage": "Export session"
},
"sessions.action.exportJson": {
"defaultMessage": "JSON"
},
"sessions.action.exportMarkdown": {
"defaultMessage": "Markdown"
},
"sessions.action.openNewWindow": {
"defaultMessage": "Open in new window"
},
@@ -3869,6 +3875,9 @@
"sessions.toast.duplicated": {
"defaultMessage": "Session \"{name}\" duplicated successfully"
},
"sessions.toast.exportFailed": {
"defaultMessage": "Failed to export session: {error}"
},
"sessions.toast.exported": {
"defaultMessage": "Session exported successfully"
},
+9
View File
@@ -3770,6 +3770,12 @@
"sessions.action.export": {
"defaultMessage": "Exportar sesión"
},
"sessions.action.exportJson": {
"defaultMessage": "JSON"
},
"sessions.action.exportMarkdown": {
"defaultMessage": "Markdown"
},
"sessions.action.openNewWindow": {
"defaultMessage": "Abrir en una ventana nueva"
},
@@ -3869,6 +3875,9 @@
"sessions.toast.duplicated": {
"defaultMessage": "Sesión \"{name}\" duplicada correctamente"
},
"sessions.toast.exportFailed": {
"defaultMessage": "No se pudo exportar la sesión: {error}"
},
"sessions.toast.exported": {
"defaultMessage": "Sesión exportada correctamente"
},
+9
View File
@@ -3770,6 +3770,12 @@
"sessions.action.export": {
"defaultMessage": "Exporter la session"
},
"sessions.action.exportJson": {
"defaultMessage": "JSON"
},
"sessions.action.exportMarkdown": {
"defaultMessage": "Markdown"
},
"sessions.action.openNewWindow": {
"defaultMessage": "Ouvrir dans une nouvelle fenêtre"
},
@@ -3869,6 +3875,9 @@
"sessions.toast.duplicated": {
"defaultMessage": "Session « {name} » dupliquée avec succès"
},
"sessions.toast.exportFailed": {
"defaultMessage": "Échec de l'exportation de la session : {error}"
},
"sessions.toast.exported": {
"defaultMessage": "Session exportée avec succès"
},
+9
View File
@@ -3770,6 +3770,12 @@
"sessions.action.export": {
"defaultMessage": "निर्यात सत्र"
},
"sessions.action.exportJson": {
"defaultMessage": "JSON"
},
"sessions.action.exportMarkdown": {
"defaultMessage": "Markdown"
},
"sessions.action.openNewWindow": {
"defaultMessage": "नई विंडो में खोलें"
},
@@ -3869,6 +3875,9 @@
"sessions.toast.duplicated": {
"defaultMessage": "सत्र \"{name}\" सफलतापूर्वक दोहराया गया"
},
"sessions.toast.exportFailed": {
"defaultMessage": "सत्र निर्यात करने में विफल: {error}"
},
"sessions.toast.exported": {
"defaultMessage": "सत्र सफलतापूर्वक निर्यात किया गया"
},
+9
View File
@@ -3770,6 +3770,12 @@
"sessions.action.export": {
"defaultMessage": "Ekspor sesi"
},
"sessions.action.exportJson": {
"defaultMessage": "JSON"
},
"sessions.action.exportMarkdown": {
"defaultMessage": "Markdown"
},
"sessions.action.openNewWindow": {
"defaultMessage": "Buka di jendela baru"
},
@@ -3869,6 +3875,9 @@
"sessions.toast.duplicated": {
"defaultMessage": "Sesi \"{name}\" berhasil digandakan"
},
"sessions.toast.exportFailed": {
"defaultMessage": "Gagal mengekspor sesi: {error}"
},
"sessions.toast.exported": {
"defaultMessage": "Sesi berhasil diekspor"
},
+9
View File
@@ -3770,6 +3770,12 @@
"sessions.action.export": {
"defaultMessage": "Esporta sessione"
},
"sessions.action.exportJson": {
"defaultMessage": "JSON"
},
"sessions.action.exportMarkdown": {
"defaultMessage": "Markdown"
},
"sessions.action.openNewWindow": {
"defaultMessage": "Apri in una nuova finestra"
},
@@ -3869,6 +3875,9 @@
"sessions.toast.duplicated": {
"defaultMessage": "Sessione \"{name}\" duplicata correttamente"
},
"sessions.toast.exportFailed": {
"defaultMessage": "Impossibile esportare la sessione: {error}"
},
"sessions.toast.exported": {
"defaultMessage": "Sessione esportata correttamente"
},
+9
View File
@@ -3770,6 +3770,12 @@
"sessions.action.export": {
"defaultMessage": "セッションをエクスポート"
},
"sessions.action.exportJson": {
"defaultMessage": "JSON"
},
"sessions.action.exportMarkdown": {
"defaultMessage": "Markdown"
},
"sessions.action.openNewWindow": {
"defaultMessage": "新しいウィンドウで開く"
},
@@ -3869,6 +3875,9 @@
"sessions.toast.duplicated": {
"defaultMessage": "セッション「{name}」を複製しました"
},
"sessions.toast.exportFailed": {
"defaultMessage": "セッションをエクスポートできませんでした: {error}"
},
"sessions.toast.exported": {
"defaultMessage": "セッションをエクスポートしました"
},
+9
View File
@@ -3770,6 +3770,12 @@
"sessions.action.export": {
"defaultMessage": "세션 내보내기"
},
"sessions.action.exportJson": {
"defaultMessage": "JSON"
},
"sessions.action.exportMarkdown": {
"defaultMessage": "Markdown"
},
"sessions.action.openNewWindow": {
"defaultMessage": "새 창에서 열기"
},
@@ -3869,6 +3875,9 @@
"sessions.toast.duplicated": {
"defaultMessage": "세션 \"{name}\"이(가) 성공적으로 복제되었습니다."
},
"sessions.toast.exportFailed": {
"defaultMessage": "세션 내보내기 실패: {error}"
},
"sessions.toast.exported": {
"defaultMessage": "세션을 성공적으로 내보냈습니다."
},
+9
View File
@@ -3770,6 +3770,12 @@
"sessions.action.export": {
"defaultMessage": "Eksport sesi"
},
"sessions.action.exportJson": {
"defaultMessage": "JSON"
},
"sessions.action.exportMarkdown": {
"defaultMessage": "Markdown"
},
"sessions.action.openNewWindow": {
"defaultMessage": "Buka dalam tetingkap baharu"
},
@@ -3869,6 +3875,9 @@
"sessions.toast.duplicated": {
"defaultMessage": "Sesi \"{name}\" berjaya diduplikasi"
},
"sessions.toast.exportFailed": {
"defaultMessage": "Gagal mengeksport sesi: {error}"
},
"sessions.toast.exported": {
"defaultMessage": "Sesi berjaya dieksport"
},
+9
View File
@@ -3770,6 +3770,12 @@
"sessions.action.export": {
"defaultMessage": "Exportar sessão"
},
"sessions.action.exportJson": {
"defaultMessage": "JSON"
},
"sessions.action.exportMarkdown": {
"defaultMessage": "Markdown"
},
"sessions.action.openNewWindow": {
"defaultMessage": "Abrir numa nova janela"
},
@@ -3869,6 +3875,9 @@
"sessions.toast.duplicated": {
"defaultMessage": "Sessão \"{name}\" duplicada com êxito"
},
"sessions.toast.exportFailed": {
"defaultMessage": "Falha ao exportar a sessão: {error}"
},
"sessions.toast.exported": {
"defaultMessage": "Sessão exportada com êxito"
},
+9
View File
@@ -3770,6 +3770,12 @@
"sessions.action.export": {
"defaultMessage": "Экспортировать сеанс"
},
"sessions.action.exportJson": {
"defaultMessage": "JSON"
},
"sessions.action.exportMarkdown": {
"defaultMessage": "Markdown"
},
"sessions.action.openNewWindow": {
"defaultMessage": "Открыть в новом окне"
},
@@ -3869,6 +3875,9 @@
"sessions.toast.duplicated": {
"defaultMessage": "Сеанс «{name}» успешно дублирован"
},
"sessions.toast.exportFailed": {
"defaultMessage": "Не удалось экспортировать сеанс: {error}"
},
"sessions.toast.exported": {
"defaultMessage": "Сеанс успешно экспортирован"
},
+9
View File
@@ -3770,6 +3770,12 @@
"sessions.action.export": {
"defaultMessage": "Oturumu dışa aktar"
},
"sessions.action.exportJson": {
"defaultMessage": "JSON"
},
"sessions.action.exportMarkdown": {
"defaultMessage": "Markdown"
},
"sessions.action.openNewWindow": {
"defaultMessage": "Yeni pencerede aç"
},
@@ -3869,6 +3875,9 @@
"sessions.toast.duplicated": {
"defaultMessage": "\"{name}\" oturumu başarıyla kopyalandı"
},
"sessions.toast.exportFailed": {
"defaultMessage": "Oturum dışa aktarılamadı: {error}"
},
"sessions.toast.exported": {
"defaultMessage": "Oturum başarıyla dışa aktarıldı"
},
+9
View File
@@ -3770,6 +3770,12 @@
"sessions.action.export": {
"defaultMessage": "Xuất phiên"
},
"sessions.action.exportJson": {
"defaultMessage": "JSON"
},
"sessions.action.exportMarkdown": {
"defaultMessage": "Markdown"
},
"sessions.action.openNewWindow": {
"defaultMessage": "Mở trong cửa sổ mới"
},
@@ -3869,6 +3875,9 @@
"sessions.toast.duplicated": {
"defaultMessage": "Đã nhân bản phiên làm việc \"{name}\" thành công"
},
"sessions.toast.exportFailed": {
"defaultMessage": "Không thể xuất phiên làm việc: {error}"
},
"sessions.toast.exported": {
"defaultMessage": "Đã xuất phiên làm việc thành công"
},
+9
View File
@@ -3770,6 +3770,12 @@
"sessions.action.export": {
"defaultMessage": "导出会话"
},
"sessions.action.exportJson": {
"defaultMessage": "JSON"
},
"sessions.action.exportMarkdown": {
"defaultMessage": "Markdown"
},
"sessions.action.openNewWindow": {
"defaultMessage": "在新窗口中打开"
},
@@ -3869,6 +3875,9 @@
"sessions.toast.duplicated": {
"defaultMessage": "会话“{name}”复制成功"
},
"sessions.toast.exportFailed": {
"defaultMessage": "导出会话失败:{error}"
},
"sessions.toast.exported": {
"defaultMessage": "会话导出成功"
},
+9
View File
@@ -3770,6 +3770,12 @@
"sessions.action.export": {
"defaultMessage": "匯出工作階段"
},
"sessions.action.exportJson": {
"defaultMessage": "JSON"
},
"sessions.action.exportMarkdown": {
"defaultMessage": "Markdown"
},
"sessions.action.openNewWindow": {
"defaultMessage": "在新視窗中開啟"
},
@@ -3869,6 +3875,9 @@
"sessions.toast.duplicated": {
"defaultMessage": "已成功複製工作階段「{name}」"
},
"sessions.toast.exportFailed": {
"defaultMessage": "無法匯出工作階段:{error}"
},
"sessions.toast.exported": {
"defaultMessage": "已成功匯出工作階段"
},
File diff suppressed because one or more lines are too long
+6 -2
View File
@@ -1477,14 +1477,18 @@ export type OnboardingImportApplyResponse_unstable = {
};
/**
* Export a session as a JSON string.
* Export a session as a JSON or markdown string.
*/
export type ExportSessionRequest_unstable = {
sessionId: string;
format?: SessionExportFormat;
};
export type SessionExportFormat = 'json' | 'markdown';
/**
* Export session response raw JSON of the goose session with `conversation`.
* Export session response raw JSON of the goose session with `conversation`,
* or a markdown transcript when `format` is `markdown`.
*/
export type ExportSessionResponse_unstable = {
data: string;
+7 -3
View File
@@ -1410,15 +1410,19 @@ export const zOnboardingImportApplyResponse_unstable = z.object({
]).optional()
});
export const zSessionExportFormat = z.enum(['json', 'markdown']);
/**
* Export a session as a JSON string.
* Export a session as a JSON or markdown string.
*/
export const zExportSessionRequest_unstable = z.object({
sessionId: z.string()
sessionId: z.string(),
format: zSessionExportFormat.optional().default('json')
});
/**
* Export session response raw JSON of the goose session with `conversation`.
* Export session response raw JSON of the goose session with `conversation`,
* or a markdown transcript when `format` is `markdown`.
*/
export const zExportSessionResponse_unstable = z.object({
data: z.string()