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:
@@ -156,7 +156,12 @@ export class AgentManagerPanelProvider {
|
|||||||
|
|
||||||
const { path, headers } = (payload || {}) as { path?: string; headers?: Record<string, string> };
|
const { path, headers } = (payload || {}) as { path?: string; headers?: Record<string, string> };
|
||||||
const normalizedPath = typeof path === 'string' && path.trim().length > 0 ? path.trim() : '/event';
|
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) {
|
if (!apiBaseUrl) {
|
||||||
return {
|
return {
|
||||||
@@ -189,7 +194,7 @@ export class AgentManagerPanelProvider {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Fallback for OpenCode versions without /global/event.
|
// 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();
|
const fallbackUrl = new URL('event', base).toString();
|
||||||
response = await fetch(fallbackUrl, {
|
response = await fetch(fallbackUrl, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@@ -259,7 +264,7 @@ export class AgentManagerPanelProvider {
|
|||||||
if (!chunk) continue;
|
if (!chunk) continue;
|
||||||
|
|
||||||
// Reduce webview message pressure by forwarding complete SSE blocks.
|
// 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');
|
const blocks = sseBuffer.split('\n\n');
|
||||||
sseBuffer = blocks.pop() ?? '';
|
sseBuffer = blocks.pop() ?? '';
|
||||||
if (blocks.length > 0) {
|
if (blocks.length > 0) {
|
||||||
@@ -275,7 +280,7 @@ export class AgentManagerPanelProvider {
|
|||||||
|
|
||||||
const tail = decoder.decode();
|
const tail = decoder.decode();
|
||||||
if (tail) {
|
if (tail) {
|
||||||
sseBuffer += shouldInjectActivity ? tail.replace(/\r\n/g, '\n') : tail;
|
sseBuffer += tail.replace(/\r\n/g, '\n');
|
||||||
}
|
}
|
||||||
if (sseBuffer) {
|
if (sseBuffer) {
|
||||||
if (shouldInjectActivity) {
|
if (shouldInjectActivity) {
|
||||||
|
|||||||
@@ -184,7 +184,12 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
|
|||||||
|
|
||||||
const { path, headers } = (payload || {}) as { path?: string; headers?: Record<string, string> };
|
const { path, headers } = (payload || {}) as { path?: string; headers?: Record<string, string> };
|
||||||
const normalizedPath = typeof path === 'string' && path.trim().length > 0 ? path.trim() : '/event';
|
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) {
|
if (!apiBaseUrl) {
|
||||||
return {
|
return {
|
||||||
@@ -218,7 +223,7 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
|
|||||||
|
|
||||||
// Fallback: OpenCode versions without /global/event.
|
// Fallback: OpenCode versions without /global/event.
|
||||||
// VS Code is single-workspace, so we can wrap /event into { directory, payload }.
|
// 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();
|
const fallbackUrl = new URL('event', base).toString();
|
||||||
response = await fetch(fallbackUrl, {
|
response = await fetch(fallbackUrl, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@@ -290,7 +295,7 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
|
|||||||
// Reduce webview message pressure by forwarding complete SSE blocks.
|
// Reduce webview message pressure by forwarding complete SSE blocks.
|
||||||
// The SDK SSE parser is block-based (\n\n delimited) and can consume
|
// 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.
|
// 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');
|
const blocks = sseBuffer.split('\n\n');
|
||||||
sseBuffer = blocks.pop() ?? '';
|
sseBuffer = blocks.pop() ?? '';
|
||||||
if (blocks.length > 0) {
|
if (blocks.length > 0) {
|
||||||
@@ -306,7 +311,7 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
|
|||||||
|
|
||||||
const tail = decoder.decode();
|
const tail = decoder.decode();
|
||||||
if (tail) {
|
if (tail) {
|
||||||
sseBuffer += shouldInjectActivity ? tail.replace(/\r\n/g, '\n') : tail;
|
sseBuffer += tail.replace(/\r\n/g, '\n');
|
||||||
}
|
}
|
||||||
if (sseBuffer) {
|
if (sseBuffer) {
|
||||||
if (shouldInjectActivity) {
|
if (shouldInjectActivity) {
|
||||||
|
|||||||
@@ -179,7 +179,12 @@ export class SessionEditorPanelProvider {
|
|||||||
|
|
||||||
const { path, headers } = (payload || {}) as { path?: string; headers?: Record<string, string> };
|
const { path, headers } = (payload || {}) as { path?: string; headers?: Record<string, string> };
|
||||||
const normalizedPath = typeof path === 'string' && path.trim().length > 0 ? path.trim() : '/event';
|
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) {
|
if (!apiBaseUrl) {
|
||||||
return {
|
return {
|
||||||
@@ -213,7 +218,7 @@ export class SessionEditorPanelProvider {
|
|||||||
|
|
||||||
// Fallback: OpenCode versions without /global/event.
|
// Fallback: OpenCode versions without /global/event.
|
||||||
// VS Code is single-workspace, so we can wrap /event into { directory, payload }.
|
// 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();
|
const fallbackUrl = new URL('event', base).toString();
|
||||||
response = await fetch(fallbackUrl, {
|
response = await fetch(fallbackUrl, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@@ -282,7 +287,7 @@ export class SessionEditorPanelProvider {
|
|||||||
const chunk = decoder.decode(value, { stream: true });
|
const chunk = decoder.decode(value, { stream: true });
|
||||||
if (!chunk) continue;
|
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');
|
const blocks = sseBuffer.split('\n\n');
|
||||||
sseBuffer = blocks.pop() ?? '';
|
sseBuffer = blocks.pop() ?? '';
|
||||||
if (blocks.length > 0) {
|
if (blocks.length > 0) {
|
||||||
@@ -298,7 +303,7 @@ export class SessionEditorPanelProvider {
|
|||||||
|
|
||||||
const tail = decoder.decode();
|
const tail = decoder.decode();
|
||||||
if (tail) {
|
if (tail) {
|
||||||
sseBuffer += shouldInjectActivity ? tail.replace(/\r\n/g, '\n') : tail;
|
sseBuffer += tail.replace(/\r\n/g, '\n');
|
||||||
}
|
}
|
||||||
if (sseBuffer) {
|
if (sseBuffer) {
|
||||||
if (shouldInjectActivity) {
|
if (shouldInjectActivity) {
|
||||||
|
|||||||
Reference in New Issue
Block a user