fix: parse actual event structure from logs

Реальная структура событий из логов:
- session.text.delta: data.sessionID + data.delta (чанки текста)
- session.text.ended: data.sessionID + data.text (полный текст)
- session.reasoning.delta: data.delta (рассуждения)
- session.reasoning.ended: data.text (полные рассуждения)

Ранний парсинг не работал — искал в event.data.delta, но
sessionID был в event.data.sessionID (не event.sessionID).

Теперь:
- session.text.delta → собираем data.delta по чанкам
- session.text.ended → заменяем на data.text (полный)
- session.reasoning.* → тоже собираем
- resolve при первом тексте
This commit is contained in:
Бородин Роман 2026-09-26 09:02:44 +03:00
parent 9276de3c94
commit 76db8b5d36
1 changed files with 45 additions and 53 deletions

View File

@ -110,65 +110,21 @@ async function runPlugin(ctx: any, options: any, dataDir: string) {
try { try {
eventData = JSON.parse(event) eventData = JSON.parse(event)
} catch { } catch {
// not JSON — log raw for debugging
log(`Raw event (not JSON): ${event.slice(0, 200)}`)
continue continue
} }
} }
const eventType = eventData?.type || "" const eventType = eventData?.type || ""
const sessionId = eventData?.sessionID || eventData?.session?.id || ""
// Log ALL session events for debugging // Session text delta — streaming text chunks
if (eventType.includes("session") || sessionId) { // Structure: { type: "session.text.delta", data: { sessionID, delta: "text" } }
const preview = JSON.stringify(eventData).slice(0, 500) if (eventType === "session.text.delta") {
log(`Event: type=${eventType}, session=${sessionId}, data=${preview}`) const sessionId = eventData?.data?.sessionID || ""
} const delta = eventData?.data?.delta || ""
if (sessionId && delta && pendingResponses.has(sessionId)) {
// Check if this event has response text for a pending prompt
if (sessionId && pendingResponses.has(sessionId)) {
const pending = pendingResponses.get(sessionId)! const pending = pendingResponses.get(sessionId)!
pending.text += delta
// Try multiple structures to extract assistant text log(`Collected delta: ${delta.length} chars, total: ${pending.text.length} chars for session ${sessionId}`)
let newText = ""
// 1. event.data.content[].text (assistant text parts)
const parts = eventData?.data?.content || eventData?.content || eventData?.update?.content
if (Array.isArray(parts)) {
for (const part of parts) {
if (part?.type === "text" && part?.text) {
newText += part.text
}
}
}
// 2. event.data.text (direct text)
if (!newText && typeof eventData?.data?.text === "string") {
newText = eventData.data.text
}
// 3. event.data.message.text (message text)
if (!newText && eventData?.data?.message?.text) {
newText = eventData.data.message.text
}
// 4. event.payload.text (synthetic message)
if (!newText && typeof eventData?.payload?.text === "string") {
newText = eventData.payload.text
}
// 5. event.update.content (simple content update)
if (!newText && typeof eventData?.update?.content === "string") {
newText = eventData.update.content
}
// 6. event.delta (streaming delta)
if (!newText && typeof eventData?.delta === "string") {
newText = eventData.delta
}
// 7. event.content (direct content)
if (!newText && typeof eventData?.content === "string") {
newText = eventData.content
}
if (newText) {
pending.text += newText
log(`Collected ${newText.length} chars, total: ${pending.text.length} chars for session ${sessionId}`)
// Resolve immediately when we get any text // Resolve immediately when we get any text
const timer = (pending as any)._safetyTimer const timer = (pending as any)._safetyTimer
if (timer) clearTimeout(timer) if (timer) clearTimeout(timer)
@ -176,6 +132,42 @@ async function runPlugin(ctx: any, options: any, dataDir: string) {
pending.resolve(pending.text) pending.resolve(pending.text)
} }
} }
// Session text ended — complete text available
// Structure: { type: "session.text.ended", data: { sessionID, text: "full text" } }
if (eventType === "session.text.ended") {
const sessionId = eventData?.data?.sessionID || ""
const fullText = eventData?.data?.text || ""
if (sessionId && fullText && pendingResponses.has(sessionId)) {
const pending = pendingResponses.get(sessionId)!
pending.text = fullText
log(`Collected ended text: ${fullText.length} chars for session ${sessionId}`)
const timer = (pending as any)._safetyTimer
if (timer) clearTimeout(timer)
pendingResponses.delete(sessionId)
pending.resolve(pending.text)
}
}
// Session reasoning delta — also collect (optional, for full response)
if (eventType === "session.reasoning.delta") {
const sessionId = eventData?.data?.sessionID || ""
const delta = eventData?.data?.delta || ""
if (sessionId && delta && pendingResponses.has(sessionId)) {
const pending = pendingResponses.get(sessionId)!
pending.text += delta
}
}
// Session reasoning ended — full reasoning text
if (eventType === "session.reasoning.ended") {
const sessionId = eventData?.data?.sessionID || ""
const text = eventData?.data?.text || ""
if (sessionId && text && pendingResponses.has(sessionId)) {
const pending = pendingResponses.get(sessionId)!
pending.text += text
}
}
} }
} catch (err) { } catch (err) {
if (String(err).includes("AbortError") || (err as { name?: string }).name === "AbortError") { if (String(err).includes("AbortError") || (err as { name?: string }).name === "AbortError") {