1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
local config = require("rsvp.config")
local Player = require("rsvp.player").Player
local function activate()
local panto = require("panto")
local ext = panto.ext
local settings, err = config.read(ext.config and ext.config.rsvp)
if not settings then error("panto-rsvp: " .. err) end
local restored, state_err
local state_ok, saved_wpm, read_err = pcall(function() return ext.state.get("rsvp.wpm") end)
if not state_ok then
state_err = "WPM restore failed: " .. tostring(saved_wpm)
elseif read_err ~= nil then
state_err = "WPM restore failed: " .. tostring(read_err)
elseif type(saved_wpm) == "number" and saved_wpm == saved_wpm and saved_wpm > 0
and saved_wpm < math.huge then
restored = math.max(settings.min_wpm, saved_wpm)
end
if restored then settings.wpm = restored end
local uv = require("luv")
local player = Player.new {
settings = settings,
persistence_error = state_err,
save_wpm = function(speed) return ext.state.set("rsvp.wpm", speed) end,
highlight = function(body) return ext.highlight(body, "markdown") end,
width = panto.text.width,
truncate = panto.text.truncate,
style = function() return ext.style() end,
new_timer = function() return uv.new_timer() end,
mount = function(component, placement) return ext.mount(component, placement) end,
input_mode = function(spec) return ext.input_mode(spec) end,
}
local function subscribe(name, handler)
ext.on(name, handler)
end
subscribe("session_ready", function()
local ok, text = pcall(function()
local latest
for _, message in ipairs(ext.agent:conversation():messages()) do
if message.role == "assistant" then
for _, block in ipairs(message.blocks or {}) do
if block.type == "text" then latest = block.text or "" end
end
end
end
return latest or ""
end)
player:snapshot_source(ok and text or "")
end)
subscribe("render_assistant_text", function(event)
player:set_source(event.text or "", false, event.index)
end)
subscribe("render_assistant_text_delta", function(event)
if event.text ~= nil then player:update_source(event.text, false, event.index) end
end)
subscribe("render_assistant_text_complete", function(event)
player:update_source(event.text or "", true, event.index)
end)
subscribe("suspend", function() player:suspend() end)
subscribe("turn_start", function() player:cancel("turn") end)
subscribe("turn_interrupt", function() player:cancel("turn") end)
subscribe("session_end", function() player:cancel("session_end") end)
ext.register_command {
name = "rsvp",
description = "Read the latest assistant text one paragraph at a time.",
handler = function(arguments)
if arguments and arguments ~= "" then return "Usage: /rsvp" end
local ok, why = player:activate()
if not ok then
return "RSVP unavailable" .. (why and (": " .. tostring(why)) or "")
end
end,
}
end
return { name = "rsvp", activate = activate }
|