mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
feat: support opencode
This commit is contained in:
@@ -47,9 +47,39 @@ function normalizePlanEntries(entries: unknown): PlanItem[] {
|
||||
|
||||
export class AcpMessageHandler {
|
||||
private readonly toolCalls = new Map<string, { name: string; input: unknown }>();
|
||||
private bufferedText = '';
|
||||
|
||||
constructor(private readonly onMessage: (message: AgentMessage) => void) {}
|
||||
|
||||
flushText(): void {
|
||||
if (!this.bufferedText) {
|
||||
return;
|
||||
}
|
||||
this.onMessage({ type: 'text', text: this.bufferedText });
|
||||
this.bufferedText = '';
|
||||
}
|
||||
|
||||
private appendTextChunk(text: string): void {
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
if (!this.bufferedText) {
|
||||
this.bufferedText = text;
|
||||
return;
|
||||
}
|
||||
if (text === this.bufferedText) {
|
||||
return;
|
||||
}
|
||||
if (text.startsWith(this.bufferedText)) {
|
||||
this.bufferedText = text;
|
||||
return;
|
||||
}
|
||||
if (this.bufferedText.startsWith(text)) {
|
||||
return;
|
||||
}
|
||||
this.bufferedText += text;
|
||||
}
|
||||
|
||||
handleUpdate(update: unknown): void {
|
||||
if (!isObject(update)) return;
|
||||
const updateType = asString(update.sessionUpdate);
|
||||
@@ -59,7 +89,7 @@ export class AcpMessageHandler {
|
||||
const content = update.content;
|
||||
const text = extractTextContent(content);
|
||||
if (text) {
|
||||
this.onMessage({ type: 'text', text });
|
||||
this.appendTextChunk(text);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -69,16 +99,19 @@ export class AcpMessageHandler {
|
||||
}
|
||||
|
||||
if (updateType === ACP_SESSION_UPDATE_TYPES.toolCall) {
|
||||
this.flushText();
|
||||
this.handleToolCall(update);
|
||||
return;
|
||||
}
|
||||
|
||||
if (updateType === ACP_SESSION_UPDATE_TYPES.toolCallUpdate) {
|
||||
this.flushText();
|
||||
this.handleToolCallUpdate(update);
|
||||
return;
|
||||
}
|
||||
|
||||
if (updateType === ACP_SESSION_UPDATE_TYPES.plan) {
|
||||
this.flushText();
|
||||
const items = normalizePlanEntries(update.entries);
|
||||
if (items.length > 0) {
|
||||
this.onMessage({ type: 'plan', items });
|
||||
|
||||
@@ -106,6 +106,31 @@ export class AcpSdkBackend implements AgentBackend {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
async loadSession(config: AgentSessionConfig & { sessionId: string }): Promise<string> {
|
||||
if (!this.transport) {
|
||||
throw new Error('ACP transport not initialized');
|
||||
}
|
||||
|
||||
const response = await withRetry(
|
||||
() => this.transport!.sendRequest('session/load', {
|
||||
sessionId: config.sessionId,
|
||||
cwd: config.cwd,
|
||||
mcpServers: config.mcpServers
|
||||
}),
|
||||
{
|
||||
...AcpSdkBackend.INIT_RETRY_OPTIONS,
|
||||
onRetry: (error, attempt, nextDelayMs) => {
|
||||
logger.debug(`[ACP] session/load attempt ${attempt} failed, retrying in ${nextDelayMs}ms`, error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const loadedSessionId = isObject(response) ? asString(response.sessionId) : null;
|
||||
const sessionId = loadedSessionId ?? config.sessionId;
|
||||
this.activeSessionId = sessionId;
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
async prompt(
|
||||
sessionId: string,
|
||||
content: PromptContent[],
|
||||
@@ -129,9 +154,11 @@ export class AcpSdkBackend implements AgentBackend {
|
||||
|
||||
const stopReason = isObject(response) ? asString(response.stopReason) : null;
|
||||
if (stopReason) {
|
||||
this.messageHandler?.flushText();
|
||||
onUpdate({ type: 'turn_complete', stopReason });
|
||||
}
|
||||
} finally {
|
||||
this.messageHandler?.flushText();
|
||||
this.messageHandler = null;
|
||||
this.isProcessingMessage = false;
|
||||
this.notifyResponseComplete();
|
||||
|
||||
Reference in New Issue
Block a user