heartbeat added

This commit is contained in:
2026-05-12 22:39:36 -04:00
parent 1a93c9246f
commit d256ffb487
+45 -7
View File
@@ -140,18 +140,35 @@ Please follow your instructions to produce the funding outlook brief.`
} }
// ----- Pipe agent activity to the client ----- // ----- Pipe agent activity to the client -----
// The agent emits multiple event types. We surface: // Long stretches between agent.message events (model "thinking",
// - agent.message text blocks (narration) // multi-tool batches, etc.) can silence the response stream long enough
// - agent.tool_use for the `write` tool, whose `input.content` IS the // that Netlify's edge proxy drops the connection. To keep bytes flowing
// final brief the agent produces (it writes it as a Markdown file in // we (a) forward extra event types as concise status lines and (b) emit
// the sandbox instead of streaming it back) // a periodic heartbeat space when the upstream goes quiet.
// - lightweight status lines for other tool calls so the UI keeps moving
const encoder = new TextEncoder() const encoder = new TextEncoder()
const seenEventIds = new Set() const seenEventIds = new Set()
const writtenFiles = new Set() // dedupe writes by path const writtenFiles = new Set() // dedupe writes by path
const HEARTBEAT_MS = 10_000
const stream = new ReadableStream({ const stream = new ReadableStream({
async start(controller) { async start(controller) {
const send = (s) => controller.enqueue(encoder.encode(s)) let lastSendAt = Date.now()
const send = (s) => {
controller.enqueue(encoder.encode(s))
lastSendAt = Date.now()
}
// Zero-width space — invisible in rendered Markdown but counts as a
// byte on the wire, which is enough to defeat proxy idle-timeouts.
const heartbeat = setInterval(() => {
if (Date.now() - lastSendAt >= HEARTBEAT_MS) {
try {
controller.enqueue(encoder.encode('\u200B'))
lastSendAt = Date.now()
} catch {
/* controller may have closed */
}
}
}, HEARTBEAT_MS / 2)
try { try {
for await (const event of upstream) { for await (const event of upstream) {
@@ -170,6 +187,11 @@ Please follow your instructions to produce the funding outlook brief.`
break break
} }
case 'agent.thinking': {
send(`\n\n_💭 thinking…_\n\n`)
break
}
case 'agent.tool_use': { case 'agent.tool_use': {
// The write tool carries the actual brief in input.content. // The write tool carries the actual brief in input.content.
if ( if (
@@ -194,6 +216,20 @@ Please follow your instructions to produce the funding outlook brief.`
break break
} }
case 'agent.tool_result':
case 'agent.mcp_tool_result': {
if (event.is_error) {
const msg =
(Array.isArray(event.content) &&
event.content[0]?.text) ||
'tool error'
send(`\n\n_⚠️ tool error: ${String(msg).slice(0, 200)}_\n\n`)
} else {
send(`\n\n_✓ result_\n\n`)
}
break
}
case 'session.error': { case 'session.error': {
const msg = event.error?.message || 'unknown session error' const msg = event.error?.message || 'unknown session error'
send(`\n\n[session error: ${msg}]`) send(`\n\n[session error: ${msg}]`)
@@ -211,8 +247,10 @@ Please follow your instructions to produce the funding outlook brief.`
break break
} }
} }
clearInterval(heartbeat)
controller.close() controller.close()
} catch (err) { } catch (err) {
clearInterval(heartbeat)
send(`\n\n[stream error: ${err.message || err}]`) send(`\n\n[stream error: ${err.message || err}]`)
controller.close() controller.close()
} }