mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
fix(opencode): preserve ACP text deltas (#1023)
This commit is contained in:
@@ -377,6 +377,25 @@ describe('AcpMessageHandler', () => {
|
||||
expect(messages).toEqual([{ type: 'text', text: 'hello world' }]);
|
||||
});
|
||||
|
||||
it('preserves overlapping text chunks in delta mode', () => {
|
||||
const messages: AgentMessage[] = [];
|
||||
const handler = new AcpMessageHandler(
|
||||
(message) => messages.push(message),
|
||||
{ textChunkMode: 'delta' }
|
||||
);
|
||||
|
||||
for (const text of ['|-----|', '-----|', '-----|\n']) {
|
||||
handler.handleUpdate({
|
||||
sessionUpdate: ACP_SESSION_UPDATE_TYPES.agentMessageChunk,
|
||||
content: { type: 'text', text }
|
||||
});
|
||||
}
|
||||
|
||||
handler.flushText();
|
||||
|
||||
expect(messages).toEqual([{ type: 'text', text: '|-----|-----|-----|\n' }]);
|
||||
});
|
||||
|
||||
it('keeps existing tool name when update only has kind fallback', () => {
|
||||
const messages: AgentMessage[] = [];
|
||||
const handler = new AcpMessageHandler((message) => messages.push(message));
|
||||
|
||||
@@ -358,6 +358,8 @@ function normalizePlanEntries(entries: unknown): PlanItem[] {
|
||||
return items;
|
||||
}
|
||||
|
||||
export type AcpTextChunkMode = 'dedupe' | 'delta';
|
||||
|
||||
function getSuffixPrefixOverlap(base: string, next: string): number {
|
||||
const maxOverlap = Math.min(base.length, next.length);
|
||||
for (let length = maxOverlap; length > 0; length -= 1) {
|
||||
@@ -380,8 +382,14 @@ export class AcpMessageHandler {
|
||||
private lastReasoningSnapshotAt: number | null = null;
|
||||
private lastReasoningSnapshotText = '';
|
||||
private reasoningSnapshotEmitted = false;
|
||||
private readonly textChunkMode: AcpTextChunkMode;
|
||||
|
||||
constructor(private readonly onMessage: (message: AgentMessage) => void) {}
|
||||
constructor(
|
||||
private readonly onMessage: (message: AgentMessage) => void,
|
||||
options: { textChunkMode?: AcpTextChunkMode } = {}
|
||||
) {
|
||||
this.textChunkMode = options.textChunkMode ?? 'dedupe';
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits any buffered assistant text as a single message and clears the
|
||||
@@ -444,6 +452,13 @@ export class AcpMessageHandler {
|
||||
}
|
||||
|
||||
private appendTextChunk(text: string): void {
|
||||
if (this.textChunkMode === 'delta') {
|
||||
if (text) {
|
||||
this.bufferedText += text;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { AgentFlavor } from '@hapi/protocol';
|
||||
import type { AgentBackend, AgentMessage, AgentSessionConfig, PermissionRequest, PermissionResponse, PromptContent } from '@/agent/types';
|
||||
import { asString, isObject } from '@hapi/protocol';
|
||||
import { AcpStdioTransport, type AcpStderrError } from './AcpStdioTransport';
|
||||
import { AcpMessageHandler } from './AcpMessageHandler';
|
||||
import { AcpMessageHandler, type AcpTextChunkMode } from './AcpMessageHandler';
|
||||
import { ACP_SESSION_UPDATE_TYPES } from './constants';
|
||||
import { logger } from '@/ui/logger';
|
||||
import { withRetry } from '@/utils/time';
|
||||
@@ -101,7 +101,12 @@ export class AcpSdkBackend implements AgentBackend {
|
||||
private static readonly LATE_FLUSH_QUIET_PERIOD_MS = 250;
|
||||
private static readonly LATE_FLUSH_WINDOW_MS = 6000;
|
||||
|
||||
constructor(private readonly options: { command: string; args?: string[]; env?: Record<string, string> }) {}
|
||||
constructor(private readonly options: {
|
||||
command: string;
|
||||
args?: string[];
|
||||
env?: Record<string, string>;
|
||||
textChunkMode?: AcpTextChunkMode;
|
||||
}) {}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
if (this.transport) return;
|
||||
@@ -401,7 +406,7 @@ export class AcpSdkBackend implements AgentBackend {
|
||||
AcpSdkBackend.PRE_PROMPT_UPDATE_DRAIN_TIMEOUT_MS
|
||||
);
|
||||
this.messageHandler?.drainBuffers();
|
||||
this.messageHandler = new AcpMessageHandler(onUpdate);
|
||||
this.messageHandler = new AcpMessageHandler(onUpdate, { textChunkMode: this.options.textChunkMode });
|
||||
this.isProcessingMessage = true;
|
||||
this.lastSessionUpdateAt = Date.now();
|
||||
this.latestUsageUpdate = null;
|
||||
|
||||
@@ -21,6 +21,7 @@ export function createOpencodeBackend(opts: {
|
||||
return new AcpSdkBackend({
|
||||
command: 'opencode',
|
||||
args,
|
||||
env: filterEnv(env)
|
||||
env: filterEnv(env),
|
||||
textChunkMode: 'delta'
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user