fix: preserve lastEventId in SSE path and add proxy heartbeat (#1041)

* fix: preserve lastEventId in SSE path and add proxy heartbeat

- Extract event.id from SSE stream events in event-pipeline.ts so that
  reconnects carry the correct Last-Event-ID header for gapless replay.
- Emit :heartbeat comment every 20s in the direct SSE proxy to keep
  the UI heartbeat watchdog from aborting idle connections.

* fix: handle SSE metadata through SDK callback

* Guard SSE proxy heartbeats

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
Jinwoo An (안진우)
2026-04-27 14:16:44 +03:00
committed by GitHub
co-authored by Bohdan Triapitsyn
parent 43c92babc3
commit 6470d9d205
3 changed files with 85 additions and 2 deletions
+12 -1
View File
@@ -3,7 +3,7 @@ import { EventEmitter } from 'node:events';
import express from 'express';
import path from 'path';
import { registerOpenCodeProxy, writeSseChunkWithBackpressure } from './lib/opencode/proxy.js';
import { createSseBoundaryTracker, registerOpenCodeProxy, writeSseChunkWithBackpressure } from './lib/opencode/proxy.js';
const listen = (app, host = '127.0.0.1') => new Promise((resolve, reject) => {
const server = app.listen(0, host, () => resolve(server));
@@ -103,6 +103,17 @@ describe('OpenCode proxy SSE forwarding', () => {
await expect(write).resolves.toBe(true);
});
it('tracks whether a raw SSE stream is between event blocks', () => {
const tracker = createSseBoundaryTracker();
expect(tracker.isAtBoundary()).toBe(true);
expect(tracker.observe(Buffer.from('id: evt-1\n'))).toBe(false);
expect(tracker.observe(Buffer.from('data: {"ok"'))).toBe(false);
expect(tracker.observe(Buffer.from(':true}\n'))).toBe(false);
expect(tracker.observe(Buffer.from('\n'))).toBe(true);
expect(tracker.observe(Buffer.from('data: next\r\n\r\n'))).toBe(true);
});
it('routes generic API requests through external OpenCode base URL', async () => {
const upstream = express();
upstream.get('/config/providers', (_req, res) => {