Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 28 additions & 11 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
type WriteTextFileResponse,
} from "@agentclientprotocol/sdk";
import { extractAcpError } from "./acp-error-shapes.js";
import { isSessionUpdateNotification } from "./acp-jsonrpc.js";
import { isAcpJsonRpcMessage, isSessionUpdateNotification } from "./acp-jsonrpc.js";
import {
AgentSpawnError,
AuthPolicyError,
Expand Down Expand Up @@ -74,6 +74,7 @@ const GEMINI_VERSION_TIMEOUT_MS = 2_000;
const GEMINI_ACP_FLAG_VERSION = [0, 33, 0] as const;
const COPILOT_HELP_TIMEOUT_MS = 2_000;
const SESSION_CONTROL_UNSUPPORTED_ACP_CODES = new Set([-32601, -32602]);
const MAX_CONSECUTIVE_NON_JSON_AGENT_OUTPUT_LINES = 10;

type LoadSessionOptions = {
suppressReplayUpdates?: boolean;
Expand Down Expand Up @@ -317,11 +318,33 @@ function createNdJsonMessageStream(
const readable = new ReadableStream<AnyMessage>({
async start(controller) {
let content = "";
let nonJsonLineCount = 0;
const reader = input.getReader();
try {
const enqueueParsedMessage = (trimmedLine: string) => {
if (!trimmedLine || shouldIgnoreNonJsonAgentOutputLine(agentCommand, trimmedLine)) {
return;
}

try {
const message = JSON.parse(trimmedLine) as AnyMessage;
controller.enqueue(message);
nonJsonLineCount = 0;
} catch {
nonJsonLineCount += 1;
if (nonJsonLineCount > MAX_CONSECUTIVE_NON_JSON_AGENT_OUTPUT_LINES) {
throw new Error(
`Agent stdout exceeded ${MAX_CONSECUTIVE_NON_JSON_AGENT_OUTPUT_LINES} non-JSON lines without completing ACP handshake. ` +
"This indicates the adapter does not support stdio ACP mode.",
);
}
}
};

while (true) {
const { value, done } = await reader.read();
if (done) {
enqueueParsedMessage(content.trim());
break;
}
if (!value) {
Expand All @@ -331,16 +354,7 @@ function createNdJsonMessageStream(
const lines = content.split("\n");
content = lines.pop() || "";
for (const line of lines) {
const trimmedLine = line.trim();
if (!trimmedLine || shouldIgnoreNonJsonAgentOutputLine(agentCommand, trimmedLine)) {
continue;
}
try {
const message = JSON.parse(trimmedLine) as AnyMessage;
controller.enqueue(message);
} catch (err) {
console.error("Failed to parse JSON message:", trimmedLine, err);
}
enqueueParsedMessage(line.trim());
}
}
} finally {
Expand Down Expand Up @@ -1137,6 +1151,9 @@ export class AcpClient {
if (!value) {
continue;
}
if (!isAcpJsonRpcMessage(value)) {
continue;
}
if (!shouldSuppressInboundReplaySessionUpdate(value)) {
onAcpOutputMessage()?.("inbound", value);
onAcpMessage()?.("inbound", value);
Expand Down
Loading