Skip to content
Open
Show file tree
Hide file tree
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
13 changes: 12 additions & 1 deletion src/CodexEventHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export class CodexEventHandler {
private readonly activeImageGenerationItems = new Set<string>();
private readonly emittedImageViewItems = new Set<string>();
private readonly seenReasoningDeltaItemIds = new Set<string>();
private readonly seenAgentMessageDeltaItemIds = new Set<string>();
private readonly terminalCommandIds = new Set<string>();
private readonly terminalCommandOutputIds = new Set<string>();

Expand Down Expand Up @@ -230,6 +231,7 @@ export class CodexEventHandler {
}

private async createTextEvent(event: AgentMessageDeltaNotification): Promise<UpdateSessionEvent> {
this.seenAgentMessageDeltaItemIds.add(event.itemId);
return createAgentTextMessageChunk(event.delta, event.itemId);
}

Expand Down Expand Up @@ -387,12 +389,21 @@ export class CodexEventHandler {
return this.createExitedReviewModeEvent(event.item);
case "contextCompaction":
return this.createContextCompactedEvent();
case "agentMessage": {
if (this.seenAgentMessageDeltaItemIds.delete(event.item.id)) {
return null;
}
const text = event.item.text;
if (text.length > 0) {
return createAgentTextMessageChunk(text, event.item.id);
}
return null;
}
//ignored types
case "subAgentActivity":
case "sleep":
case "userMessage":
case "hookPrompt":
case "agentMessage":
case "enteredReviewMode":
case "plan":
return null;
Expand Down
96 changes: 96 additions & 0 deletions src/__tests__/CodexACPAgent/agent-message-fallback.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { ServerNotification } from "../../app-server";
import type { SessionState } from "../../CodexAcpServer";
import { AgentMode } from "../../AgentMode";
import {
createCodexMockTestFixture,
createTestSessionState,
setupPromptAndSendNotifications,
type CodexMockTestFixture
} from "../acp-test-utils";

describe("CodexEventHandler - agent message fallback", () => {
let mockFixture: CodexMockTestFixture;
const sessionId = "test-session-id";

beforeEach(() => {
mockFixture = createCodexMockTestFixture();
vi.clearAllMocks();
});

const sessionState: SessionState = createTestSessionState({
sessionId,
currentModelId: "model-id[effort]",
agentMode: AgentMode.DEFAULT_AGENT_MODE
});

it("emits agent message text from item/completed when no deltas streamed", async () => {
const notifications: ServerNotification[] = [
{
method: "item/completed",
params: {
threadId: sessionId,
turnId: "turn-1",
completedAtMs: 0,
item: {
type: "agentMessage",
id: "msg-1",
text: "Hello!",
phase: null,
memoryCitation: null,
},
},
},
];

await setupPromptAndSendNotifications(mockFixture, sessionId, sessionState, notifications);

await expect(mockFixture.getAcpConnectionDump([])).toMatchFileSnapshot(
"data/agent-message-completed-fallback.json"
);
});

it("does not duplicate agent message when deltas were streamed", async () => {
const notifications: ServerNotification[] = [
{
method: "item/agentMessage/delta",
params: {
threadId: sessionId,
turnId: "turn-1",
itemId: "msg-2",
delta: "Hel",
},
},
{
method: "item/agentMessage/delta",
params: {
threadId: sessionId,
turnId: "turn-1",
itemId: "msg-2",
delta: "lo!",
},
},
{
method: "item/completed",
params: {
threadId: sessionId,
turnId: "turn-1",
completedAtMs: 0,
item: {
type: "agentMessage",
id: "msg-2",
text: "Hello!",
phase: null,
memoryCitation: null,
},
},
},
];

await setupPromptAndSendNotifications(mockFixture, sessionId, sessionState, notifications);

await expect(mockFixture.getAcpConnectionDump([])).toMatchFileSnapshot(
"data/agent-message-deltas-no-duplicate.json"
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"method": "sessionUpdate",
"args": [
{
"sessionId": "test-session-id",
"update": {
"sessionUpdate": "agent_message_chunk",
"messageId": "msg-1",
"content": {
"type": "text",
"text": "Hello!"
}
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"method": "sessionUpdate",
"args": [
{
"sessionId": "test-session-id",
"update": {
"sessionUpdate": "agent_message_chunk",
"messageId": "msg-2",
"content": {
"type": "text",
"text": "Hel"
}
}
}
]
}
{
"method": "sessionUpdate",
"args": [
{
"sessionId": "test-session-id",
"update": {
"sessionUpdate": "agent_message_chunk",
"messageId": "msg-2",
"content": {
"type": "text",
"text": "lo!"
}
}
}
]
}