fix: adjust live VS Code streaming updates

- Handle SSE paths with query strings and trailing slashes correctly
- Normalize streamed chunk newlines before SSE block parsing
- Apply streaming fixes across chat, session editor, and agent manager panels
This commit is contained in:
Bohdan Triapitsyn
2026-03-01 01:42:58 +02:00
parent 8bfbed0e68
commit ebb2a85a4f
3 changed files with 27 additions and 12 deletions
@@ -156,7 +156,12 @@ export class AgentManagerPanelProvider {
const { path, headers } = (payload || {}) as { path?: string; headers?: Record<string, string> };
const normalizedPath = typeof path === 'string' && path.trim().length > 0 ? path.trim() : '/event';
const shouldInjectActivity = normalizedPath === '/event' || normalizedPath === '/global/event';
const normalizedPathname = (() => {
const rawPathname = normalizedPath.split('?')[0];
if (rawPathname === '/') return '/';
return rawPathname.replace(/\/+$/, '');
})();
const shouldInjectActivity = normalizedPathname === '/event' || normalizedPathname === '/global/event';
if (!apiBaseUrl) {
return {
@@ -189,7 +194,7 @@ export class AgentManagerPanelProvider {
});
// Fallback for OpenCode versions without /global/event.
if ((!response.ok || !response.body) && normalizedPath === '/global/event') {
if ((!response.ok || !response.body) && normalizedPathname === '/global/event') {
const fallbackUrl = new URL('event', base).toString();
response = await fetch(fallbackUrl, {
method: 'GET',
@@ -259,7 +264,7 @@ export class AgentManagerPanelProvider {
if (!chunk) continue;
// Reduce webview message pressure by forwarding complete SSE blocks.
sseBuffer += shouldInjectActivity ? chunk.replace(/\r\n/g, '\n') : chunk;
sseBuffer += chunk.replace(/\r\n/g, '\n');
const blocks = sseBuffer.split('\n\n');
sseBuffer = blocks.pop() ?? '';
if (blocks.length > 0) {
@@ -275,7 +280,7 @@ export class AgentManagerPanelProvider {
const tail = decoder.decode();
if (tail) {
sseBuffer += shouldInjectActivity ? tail.replace(/\r\n/g, '\n') : tail;
sseBuffer += tail.replace(/\r\n/g, '\n');
}
if (sseBuffer) {
if (shouldInjectActivity) {
+9 -4
View File
@@ -184,7 +184,12 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
const { path, headers } = (payload || {}) as { path?: string; headers?: Record<string, string> };
const normalizedPath = typeof path === 'string' && path.trim().length > 0 ? path.trim() : '/event';
const shouldInjectActivity = normalizedPath === '/event' || normalizedPath === '/global/event';
const normalizedPathname = (() => {
const rawPathname = normalizedPath.split('?')[0];
if (rawPathname === '/') return '/';
return rawPathname.replace(/\/+$/, '');
})();
const shouldInjectActivity = normalizedPathname === '/event' || normalizedPathname === '/global/event';
if (!apiBaseUrl) {
return {
@@ -218,7 +223,7 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
// Fallback: OpenCode versions without /global/event.
// VS Code is single-workspace, so we can wrap /event into { directory, payload }.
if ((!response.ok || !response.body) && normalizedPath === '/global/event') {
if ((!response.ok || !response.body) && normalizedPathname === '/global/event') {
const fallbackUrl = new URL('event', base).toString();
response = await fetch(fallbackUrl, {
method: 'GET',
@@ -290,7 +295,7 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
// Reduce webview message pressure by forwarding complete SSE blocks.
// The SDK SSE parser is block-based (\n\n delimited) and can consume
// partial chunks, but VS Code's postMessage channel can be a bottleneck.
sseBuffer += shouldInjectActivity ? chunk.replace(/\r\n/g, '\n') : chunk;
sseBuffer += chunk.replace(/\r\n/g, '\n');
const blocks = sseBuffer.split('\n\n');
sseBuffer = blocks.pop() ?? '';
if (blocks.length > 0) {
@@ -306,7 +311,7 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
const tail = decoder.decode();
if (tail) {
sseBuffer += shouldInjectActivity ? tail.replace(/\r\n/g, '\n') : tail;
sseBuffer += tail.replace(/\r\n/g, '\n');
}
if (sseBuffer) {
if (shouldInjectActivity) {
@@ -179,7 +179,12 @@ export class SessionEditorPanelProvider {
const { path, headers } = (payload || {}) as { path?: string; headers?: Record<string, string> };
const normalizedPath = typeof path === 'string' && path.trim().length > 0 ? path.trim() : '/event';
const shouldInjectActivity = normalizedPath === '/event' || normalizedPath === '/global/event';
const normalizedPathname = (() => {
const rawPathname = normalizedPath.split('?')[0];
if (rawPathname === '/') return '/';
return rawPathname.replace(/\/+$/, '');
})();
const shouldInjectActivity = normalizedPathname === '/event' || normalizedPathname === '/global/event';
if (!apiBaseUrl) {
return {
@@ -213,7 +218,7 @@ export class SessionEditorPanelProvider {
// Fallback: OpenCode versions without /global/event.
// VS Code is single-workspace, so we can wrap /event into { directory, payload }.
if ((!response.ok || !response.body) && normalizedPath === '/global/event') {
if ((!response.ok || !response.body) && normalizedPathname === '/global/event') {
const fallbackUrl = new URL('event', base).toString();
response = await fetch(fallbackUrl, {
method: 'GET',
@@ -282,7 +287,7 @@ export class SessionEditorPanelProvider {
const chunk = decoder.decode(value, { stream: true });
if (!chunk) continue;
sseBuffer += shouldInjectActivity ? chunk.replace(/\r\n/g, '\n') : chunk;
sseBuffer += chunk.replace(/\r\n/g, '\n');
const blocks = sseBuffer.split('\n\n');
sseBuffer = blocks.pop() ?? '';
if (blocks.length > 0) {
@@ -298,7 +303,7 @@ export class SessionEditorPanelProvider {
const tail = decoder.decode();
if (tail) {
sseBuffer += shouldInjectActivity ? tail.replace(/\r\n/g, '\n') : tail;
sseBuffer += tail.replace(/\r\n/g, '\n');
}
if (sseBuffer) {
if (shouldInjectActivity) {