UNPKG

buroventures-harald-code

Version:

Harald Code - AI-powered coding assistant CLI

193 lines 6.33 kB
/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ export class ClientConnection { #connection; constructor(agent, input, output) { this.#connection = new Connection(agent(this), input, output); } /** * Streams part of an assistant response to the client */ async streamAssistantMessageChunk(params) { await this.#connection.sendRequest('streamAssistantMessageChunk', params); } /** * Request confirmation before running a tool * * When allowed, the client returns a [`ToolCallId`] which can be used * to update the tool call's `status` and `content` as it runs. */ requestToolCallConfirmation(params) { return this.#connection.sendRequest('requestToolCallConfirmation', params); } /** * pushToolCall allows the agent to start a tool call * when it does not need to request permission to do so. * * The returned id can be used to update the UI for the tool * call as needed. */ pushToolCall(params) { return this.#connection.sendRequest('pushToolCall', params); } /** * updateToolCall allows the agent to update the content and status of the tool call. * * The new content replaces what is currently displayed in the UI. * * The [`ToolCallId`] is included in the response of * `pushToolCall` or `requestToolCallConfirmation` respectively. */ async updateToolCall(params) { await this.#connection.sendRequest('updateToolCall', params); } } class Connection { #pendingResponses = new Map(); #nextRequestId = 0; #delegate; #peerInput; #writeQueue = Promise.resolve(); #textEncoder; constructor(delegate, peerInput, peerOutput) { this.#peerInput = peerInput; this.#textEncoder = new TextEncoder(); this.#delegate = delegate; this.#receive(peerOutput); } async #receive(output) { let content = ''; const decoder = new TextDecoder(); for await (const chunk of output) { content += decoder.decode(chunk, { stream: true }); const lines = content.split('\n'); content = lines.pop() || ''; for (const line of lines) { const trimmedLine = line.trim(); if (trimmedLine) { const message = JSON.parse(trimmedLine); this.#processMessage(message); } } } } async #processMessage(message) { if ('method' in message) { const response = await this.#tryCallDelegateMethod(message.method, message.params); await this.#sendMessage({ jsonrpc: '2.0', id: message.id, ...response, }); } else { this.#handleResponse(message); } } async #tryCallDelegateMethod(method, params) { const methodName = method; if (typeof this.#delegate[methodName] !== 'function') { return RequestError.methodNotFound(method).toResult(); } try { const result = await this.#delegate[methodName](params); return { result: result ?? null }; } catch (error) { if (error instanceof RequestError) { return error.toResult(); } let details; if (error instanceof Error) { details = error.message; } else if (typeof error === 'object' && error != null && 'message' in error && typeof error.message === 'string') { details = error.message; } return RequestError.internalError(details).toResult(); } } #handleResponse(response) { const pendingResponse = this.#pendingResponses.get(response.id); if (pendingResponse) { if ('result' in response) { pendingResponse.resolve(response.result); } else if ('error' in response) { pendingResponse.reject(response.error); } this.#pendingResponses.delete(response.id); } } async sendRequest(method, params) { const id = this.#nextRequestId++; const responsePromise = new Promise((resolve, reject) => { this.#pendingResponses.set(id, { resolve, reject }); }); await this.#sendMessage({ jsonrpc: '2.0', id, method, params }); return responsePromise; } async #sendMessage(json) { const content = JSON.stringify(json) + '\n'; this.#writeQueue = this.#writeQueue .then(async () => { const writer = this.#peerInput.getWriter(); try { await writer.write(this.#textEncoder.encode(content)); } finally { writer.releaseLock(); } }) .catch((error) => { // Continue processing writes on error console.error('ACP write error:', error); }); return this.#writeQueue; } } export class RequestError extends Error { code; data; constructor(code, message, details) { super(message); this.code = code; this.name = 'RequestError'; if (details) { this.data = { details }; } } static parseError(details) { return new RequestError(-32700, 'Parse error', details); } static invalidRequest(details) { return new RequestError(-32600, 'Invalid request', details); } static methodNotFound(details) { return new RequestError(-32601, 'Method not found', details); } static invalidParams(details) { return new RequestError(-32602, 'Invalid params', details); } static internalError(details) { return new RequestError(-32603, 'Internal error', details); } toResult() { return { error: { code: this.code, message: this.message, data: this.data, }, }; } } // Protocol types export const LATEST_PROTOCOL_VERSION = '0.0.9'; //# sourceMappingURL=acp.js.map