feat: restore reasoning in spoiler block

Reasoning собирается из session.reasoning.delta/ended и включается
в ответ перед основным текстом:

<details>
<summary>Thinking</summary>

reasoning text

</details>

---

main text
This commit is contained in:
Бородин Роман 2026-09-26 09:32:23 +03:00
parent 280eaa1e9c
commit ca4863feb8
1 changed files with 26 additions and 6 deletions

View File

@ -93,10 +93,11 @@ async function runPlugin(ctx: any, options: any, dataDir: string) {
// Track Matrix context per OpenCode session
const sessionContextMap = new Map<string, { roomId: string; threadRootId: string; context: any }>()
// Pending prompt responses — maps sessionID -> { resolve, reject }
// Pending prompt responses — maps sessionID -> { resolve, reject, reasoning }
const pendingResponses = new Map<string, {
resolve: (text: string) => void
reject: (err: any) => void
reasoning: string
}>()
// V2 Event subscription — waits for response completion signals
@ -112,21 +113,41 @@ async function runPlugin(ctx: any, options: any, dataDir: string) {
const eventType = eventData?.type || ""
const sessionId = eventData?.data?.sessionID || ""
// Session reasoning delta — streaming reasoning chunks
if (eventType === "session.reasoning.delta" && sessionId && pendingResponses.has(sessionId)) {
const delta = eventData?.data?.delta || ""
pendingResponses.get(sessionId)!.reasoning += delta
continue
}
// Session reasoning ended — full reasoning text
if (eventType === "session.reasoning.ended" && sessionId && pendingResponses.has(sessionId)) {
const text = eventData?.data?.text || ""
pendingResponses.get(sessionId)!.reasoning = text
continue
}
// Response text complete — full assembled text available
// Structure: { type: "session.text.ended", data: { sessionID, text: "full text" } }
if (eventType === "session.text.ended" && sessionId && pendingResponses.has(sessionId)) {
const fullText = eventData?.data?.text || ""
const pending = pendingResponses.get(sessionId)!
log(`Response ready: ${fullText.length} chars for session ${sessionId}`)
pendingResponses.delete(sessionId)
pending.resolve(fullText)
// Combine reasoning (in spoiler) + main text
let responseText = fullText
if (pending.reasoning) {
responseText = `<details>\n<summary>Thinking</summary>\n\n${pending.reasoning}\n\n</details>\n\n---\n\n${fullText}`
}
log(`Response ready: ${responseText.length} chars (${pending.reasoning ? 'with reasoning' : 'no reasoning'}) for session ${sessionId}`)
pending.resolve(responseText)
continue
}
// Execution failed — session errored
if (eventType === "session.execution.failed" && sessionId && pendingResponses.has(sessionId)) {
const pending = pendingResponses.get(sessionId)!
log(`Session ${sessionId} execution failed`)
pendingResponses.delete(sessionId)
pending.reject(new Error("Session execution failed"))
continue
@ -135,7 +156,6 @@ async function runPlugin(ctx: any, options: any, dataDir: string) {
// Execution interrupted — session was interrupted
if (eventType === "session.execution.interrupted" && sessionId && pendingResponses.has(sessionId)) {
const pending = pendingResponses.get(sessionId)!
log(`Session ${sessionId} execution interrupted`)
pendingResponses.delete(sessionId)
pending.reject(new Error("Session execution interrupted"))
continue
@ -222,7 +242,7 @@ async function runPlugin(ctx: any, options: any, dataDir: string) {
// Resolved by: session.text.ended (success)
// Rejected by: session.execution.failed / session.execution.interrupted
const responsePromise = new Promise<string>((resolve, reject) => {
pendingResponses.set(opencodeSessionId, { resolve, reject })
pendingResponses.set(opencodeSessionId, { resolve, reject, reasoning: "" })
})
const responseText = await responsePromise