heartbeat added
This commit is contained in:
@@ -140,18 +140,35 @@ Please follow your instructions to produce the funding outlook brief.`
|
||||
}
|
||||
|
||||
// ----- Pipe agent activity to the client -----
|
||||
// The agent emits multiple event types. We surface:
|
||||
// - agent.message text blocks (narration)
|
||||
// - agent.tool_use for the `write` tool, whose `input.content` IS the
|
||||
// final brief the agent produces (it writes it as a Markdown file in
|
||||
// the sandbox instead of streaming it back)
|
||||
// - lightweight status lines for other tool calls so the UI keeps moving
|
||||
// Long stretches between agent.message events (model "thinking",
|
||||
// multi-tool batches, etc.) can silence the response stream long enough
|
||||
// that Netlify's edge proxy drops the connection. To keep bytes flowing
|
||||
// we (a) forward extra event types as concise status lines and (b) emit
|
||||
// a periodic heartbeat space when the upstream goes quiet.
|
||||
const encoder = new TextEncoder()
|
||||
const seenEventIds = new Set()
|
||||
const writtenFiles = new Set() // dedupe writes by path
|
||||
const HEARTBEAT_MS = 10_000
|
||||
|
||||
const stream = new ReadableStream({
|
||||
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 {
|
||||
for await (const event of upstream) {
|
||||
@@ -170,6 +187,11 @@ Please follow your instructions to produce the funding outlook brief.`
|
||||
break
|
||||
}
|
||||
|
||||
case 'agent.thinking': {
|
||||
send(`\n\n_💭 thinking…_\n\n`)
|
||||
break
|
||||
}
|
||||
|
||||
case 'agent.tool_use': {
|
||||
// The write tool carries the actual brief in input.content.
|
||||
if (
|
||||
@@ -194,6 +216,20 @@ Please follow your instructions to produce the funding outlook brief.`
|
||||
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': {
|
||||
const msg = event.error?.message || 'unknown session error'
|
||||
send(`\n\n[session error: ${msg}]`)
|
||||
@@ -211,8 +247,10 @@ Please follow your instructions to produce the funding outlook brief.`
|
||||
break
|
||||
}
|
||||
}
|
||||
clearInterval(heartbeat)
|
||||
controller.close()
|
||||
} catch (err) {
|
||||
clearInterval(heartbeat)
|
||||
send(`\n\n[stream error: ${err.message || err}]`)
|
||||
controller.close()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user