Export tool calls in saved TUI sessions
ober
34f8ec84334427317b1c5ed15e92ddba2637a448
--- a/src/jcode/ui/tui.ss +++ b/src/jcode/ui/tui.ss @@ -1641,8 +1641,49 @@ (msg-block-system (format "Copied last reply to ~a" path))))))))) ;; ---- /save [path] ---- -;; Dump the visible message thread to a markdown file. Sessions persist in -;; ~/.jcode/sessions.db but a plain-text artifact is easier to share/diff. +;; Dump the message thread to a markdown file. Prefer the persisted session +;; transcript so tool calls/results are present; the visible TUI intentionally +;; hides most tool traffic to stay readable. +(def (tool-call->markdown tc port) + (display "- " port) + (display (tool-call-name tc) port) + (display "\n\n```json\n" port) + (display (tool-call-arguments tc) port) + (display "\n```\n\n" port)) + +(def (stored-message->markdown msg port) + (let ((role (message-role msg)) + (content (message-content msg)) + (tcs (message-tool-calls msg))) + (cond + ((equal? role "system") (void)) + ((equal? role "user") + (display "## user\n\n" port) + (display (or content "") port) + (display "\n\n" port)) + ((equal? role "assistant") + (display "## assistant\n\n" port) + (when (and content (not (string-empty? (string-trim content)))) + (display content port) + (display "\n\n" port)) + (when (and tcs (pair? tcs)) + (display "### tool calls\n\n" port) + (for-each (lambda (tc) (tool-call->markdown tc port)) tcs))) + ((equal? role "tool") + (display "### tool result" port) + (when (message-tool-call-id msg) + (display " " port) + (display (message-tool-call-id msg) port)) + (display "\n\n```\n" port) + (display (or content "") port) + (display "\n```\n\n" port)) + (else + (display "### " port) + (display role port) + (display "\n\n" port) + (display (or content "") port) + (display "\n\n" port))))) + (def (msg-block->markdown blk port) (case (msg-block-role blk) ((user) @@ -1687,9 +1728,15 @@ (lambda (p) (display "# jcode session " p) (display sid p) (newline p) (newline p) - (for-each - (lambda (m) (msg-block->markdown m p)) - (app-state-messages state)))) + (let ((saved (and (app-state-session-id state) + (session-load (app-state-session-id state))))) + (if saved + (for-each + (lambda (m) (stored-message->markdown m p)) + (session-messages saved)) + (for-each + (lambda (m) (msg-block->markdown m p)) + (app-state-messages state)))))) (add-message! state (msg-block-system (format "Saved session to ~a" path)))))) @@ -1912,22 +1959,24 @@ ;; the next reply and corrupted its leading chars (e.g. "Based on" shown as ;; "<U+FFFD>ased on"). Keying reuse on content==stream-buf makes streaming ;; robust to any preceding block. - (let* ((msgs (app-state-messages state)) - (last (and (pair? msgs) (car (reverse msgs)))) - (reuse? (and last - (streamed-reply-role? (msg-block-role last)) - (equal? (msg-block-content last) - (app-state-stream-buf state))))) - (tui-log "tui-stream-token: reuse?=~a token-len=~a" reuse? (string-length token)) - (unless reuse? - (tui-log "tui-stream-token: opening fresh assistant block") - (app-state-stream-buf-set! state "") - (add-message! state (msg-block-assistant ""))) - (let ((new-buf (string-append (app-state-stream-buf state) token))) - (app-state-stream-buf-set! state new-buf) - (update-last-assistant! state new-buf) - (app-state-scroll-offset-set! state 0) - (app-state-dirty?-set! state #t)))) + (unless (and (string-empty? (app-state-stream-buf state)) + (string-empty? (string-trim token))) + (let* ((msgs (app-state-messages state)) + (last (and (pair? msgs) (car (reverse msgs)))) + (reuse? (and last + (streamed-reply-role? (msg-block-role last)) + (equal? (msg-block-content last) + (app-state-stream-buf state))))) + (tui-log "tui-stream-token: reuse?=~a token-len=~a" reuse? (string-length token)) + (unless reuse? + (tui-log "tui-stream-token: opening fresh assistant block") + (app-state-stream-buf-set! state "") + (add-message! state (msg-block-assistant ""))) + (let ((new-buf (string-append (app-state-stream-buf state) token))) + (app-state-stream-buf-set! state new-buf) + (update-last-assistant! state new-buf) + (app-state-scroll-offset-set! state 0) + (app-state-dirty?-set! state #t))))) (def (tui-tool-event! state event name args) (case event