Hide OpenCode compaction summaries
This commit is contained in:
@@ -112,6 +112,66 @@ function assistantTurnEvents({
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function manualCompactEvents({
|
||||||
|
sessionId = "session-1",
|
||||||
|
summaryText = "## Goal\n- Preserve context while continuing the task.",
|
||||||
|
}: {
|
||||||
|
sessionId?: string;
|
||||||
|
summaryText?: string;
|
||||||
|
} = {}): OpenCodeEvent[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: "message.updated",
|
||||||
|
properties: {
|
||||||
|
info: {
|
||||||
|
id: "msg_compact_user",
|
||||||
|
sessionID: sessionId,
|
||||||
|
role: "user",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "message.part.updated",
|
||||||
|
properties: {
|
||||||
|
part: {
|
||||||
|
id: "prt_compact",
|
||||||
|
sessionID: sessionId,
|
||||||
|
messageID: "msg_compact_user",
|
||||||
|
type: "compaction",
|
||||||
|
auto: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "message.updated",
|
||||||
|
properties: {
|
||||||
|
info: {
|
||||||
|
id: "msg_compact_summary",
|
||||||
|
sessionID: sessionId,
|
||||||
|
role: "assistant",
|
||||||
|
providerID: "test-provider",
|
||||||
|
modelID: "gpt-5.5",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "message.part.updated",
|
||||||
|
properties: {
|
||||||
|
part: {
|
||||||
|
id: "prt_compact_summary",
|
||||||
|
sessionID: sessionId,
|
||||||
|
messageID: "msg_compact_summary",
|
||||||
|
type: "text",
|
||||||
|
text: summaryText,
|
||||||
|
time: { start: 1, end: 2 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ type: "session.compacted", properties: { sessionID: sessionId } },
|
||||||
|
{ type: "session.idle", properties: { sessionID: sessionId } },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
describe("OpenCodeAgentClient adapter smoke tests", () => {
|
describe("OpenCodeAgentClient adapter smoke tests", () => {
|
||||||
const logger = createTestLogger();
|
const logger = createTestLogger();
|
||||||
const buildConfig = (cwd: string): AgentSessionConfig => ({
|
const buildConfig = (cwd: string): AgentSessionConfig => ({
|
||||||
@@ -168,6 +228,41 @@ describe("OpenCodeAgentClient adapter smoke tests", () => {
|
|||||||
rmSync(cwd, { recursive: true, force: true });
|
rmSync(cwd, { recursive: true, force: true });
|
||||||
}, 120_000);
|
}, 120_000);
|
||||||
|
|
||||||
|
test("manual compact hides the generated summary text", async () => {
|
||||||
|
const cwd = tmpCwd();
|
||||||
|
const runtime = new TestOpenCodeRuntime();
|
||||||
|
const openCodeClient = new TestOpenCodeClient();
|
||||||
|
openCodeClient.sessionSummarizeEvents = manualCompactEvents();
|
||||||
|
runtime.enqueueClient(openCodeClient);
|
||||||
|
const client = new OpenCodeAgentClient(logger, undefined, { runtime });
|
||||||
|
const session = await client.createSession({
|
||||||
|
provider: "opencode",
|
||||||
|
cwd,
|
||||||
|
model: "test-provider/gpt-5.5",
|
||||||
|
});
|
||||||
|
|
||||||
|
const turn = await collectTurnEvents(streamSession(session, "/compact"));
|
||||||
|
|
||||||
|
expect(turn.turnCompleted).toBe(true);
|
||||||
|
expect(turn.assistantMessages).toEqual([]);
|
||||||
|
expect(turn.allTimelineItems).toEqual([
|
||||||
|
{ type: "user_message", text: "/compact", messageId: "msg_compact_user" },
|
||||||
|
{ type: "compaction", status: "loading", trigger: "manual" },
|
||||||
|
{ type: "compaction", status: "completed" },
|
||||||
|
]);
|
||||||
|
expect(openCodeClient.calls.sessionSummarize).toEqual([
|
||||||
|
expect.objectContaining({
|
||||||
|
sessionID: "session-1",
|
||||||
|
directory: cwd,
|
||||||
|
providerID: "test-provider",
|
||||||
|
modelID: "gpt-5.5",
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await session.close();
|
||||||
|
rmSync(cwd, { recursive: true, force: true });
|
||||||
|
}, 120_000);
|
||||||
|
|
||||||
test("listModels returns models with required fields", async () => {
|
test("listModels returns models with required fields", async () => {
|
||||||
const runtime = new TestOpenCodeRuntime();
|
const runtime = new TestOpenCodeRuntime();
|
||||||
const openCodeClient = new TestOpenCodeClient();
|
const openCodeClient = new TestOpenCodeClient();
|
||||||
@@ -1595,6 +1690,155 @@ describe("OpenCodeAgentClient env", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("OpenCode persisted sessions", () => {
|
describe("OpenCode persisted sessions", () => {
|
||||||
|
test("replay hides summaries produced by manual compact", () => {
|
||||||
|
const timeline = __openCodeInternals.buildOpenCodeSessionTimeline([
|
||||||
|
{
|
||||||
|
info: {
|
||||||
|
id: "msg_compact_user",
|
||||||
|
sessionID: "ses_1",
|
||||||
|
role: "user",
|
||||||
|
time: { created: 1000 },
|
||||||
|
agent: "build",
|
||||||
|
model: { providerID: "test-provider", modelID: "gpt-5.5" },
|
||||||
|
},
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
id: "prt_compact_text",
|
||||||
|
sessionID: "ses_1",
|
||||||
|
messageID: "msg_compact_user",
|
||||||
|
type: "text",
|
||||||
|
text: "/compact",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "prt_compact",
|
||||||
|
sessionID: "ses_1",
|
||||||
|
messageID: "msg_compact_user",
|
||||||
|
type: "compaction",
|
||||||
|
auto: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
info: {
|
||||||
|
id: "msg_compact_summary",
|
||||||
|
sessionID: "ses_1",
|
||||||
|
role: "assistant",
|
||||||
|
time: { created: 1001, completed: 1002 },
|
||||||
|
providerID: "test-provider",
|
||||||
|
modelID: "gpt-5.5",
|
||||||
|
},
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
id: "prt_summary",
|
||||||
|
sessionID: "ses_1",
|
||||||
|
messageID: "msg_compact_summary",
|
||||||
|
type: "text",
|
||||||
|
text: "## Goal\n- Preserve context while continuing the task.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
info: {
|
||||||
|
id: "msg_next_user",
|
||||||
|
sessionID: "ses_1",
|
||||||
|
role: "user",
|
||||||
|
time: { created: 1003 },
|
||||||
|
agent: "build",
|
||||||
|
model: { providerID: "test-provider", modelID: "gpt-5.5" },
|
||||||
|
},
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
id: "prt_next_user",
|
||||||
|
sessionID: "ses_1",
|
||||||
|
messageID: "msg_next_user",
|
||||||
|
type: "text",
|
||||||
|
text: "continue",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(timeline).toEqual([
|
||||||
|
{ type: "user_message", text: "/compact", messageId: "msg_compact_user" },
|
||||||
|
{ type: "compaction", status: "completed", trigger: "manual" },
|
||||||
|
{ type: "user_message", text: "continue", messageId: "msg_next_user" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("replay suppresses OpenCode compaction summary messages", () => {
|
||||||
|
const timeline = __openCodeInternals.buildOpenCodeSessionTimeline([
|
||||||
|
{
|
||||||
|
info: {
|
||||||
|
id: "msg_compaction_user",
|
||||||
|
sessionID: "ses_1",
|
||||||
|
role: "user",
|
||||||
|
time: { created: 1000 },
|
||||||
|
agent: "build",
|
||||||
|
model: { providerID: "opencode", modelID: "big-pickle" },
|
||||||
|
},
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
id: "prt_compaction",
|
||||||
|
sessionID: "ses_1",
|
||||||
|
messageID: "msg_compaction_user",
|
||||||
|
type: "compaction",
|
||||||
|
auto: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
info: {
|
||||||
|
id: "msg_compaction_summary",
|
||||||
|
sessionID: "ses_1",
|
||||||
|
role: "assistant",
|
||||||
|
time: { created: 1001, completed: 1002 },
|
||||||
|
parentID: "msg_compaction_user",
|
||||||
|
providerID: "opencode",
|
||||||
|
modelID: "big-pickle",
|
||||||
|
mode: "compaction",
|
||||||
|
agent: "compaction",
|
||||||
|
path: { cwd: "/workspace/repo", root: "/workspace/repo" },
|
||||||
|
summary: true,
|
||||||
|
cost: 0,
|
||||||
|
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
|
||||||
|
},
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
id: "prt_summary",
|
||||||
|
sessionID: "ses_1",
|
||||||
|
messageID: "msg_compaction_summary",
|
||||||
|
type: "text",
|
||||||
|
text: "## Goal\n- Preserve context while continuing the task.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
info: {
|
||||||
|
id: "msg_user_after_compaction",
|
||||||
|
sessionID: "ses_1",
|
||||||
|
role: "user",
|
||||||
|
time: { created: 1003 },
|
||||||
|
agent: "build",
|
||||||
|
model: { providerID: "opencode", modelID: "big-pickle" },
|
||||||
|
},
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
id: "prt_user_after_compaction",
|
||||||
|
sessionID: "ses_1",
|
||||||
|
messageID: "msg_user_after_compaction",
|
||||||
|
type: "text",
|
||||||
|
text: "/create-pr",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(timeline).toEqual([
|
||||||
|
{ type: "compaction", status: "completed", trigger: "auto" },
|
||||||
|
{ type: "user_message", text: "/create-pr", messageId: "msg_user_after_compaction" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
test("listPersistedAgents returns only sessions whose cwd matches the requested cwd", async () => {
|
test("listPersistedAgents returns only sessions whose cwd matches the requested cwd", async () => {
|
||||||
const runtime = new TestOpenCodeRuntime();
|
const runtime = new TestOpenCodeRuntime();
|
||||||
const openCodeClient = new TestOpenCodeClient();
|
const openCodeClient = new TestOpenCodeClient();
|
||||||
|
|||||||
@@ -1040,6 +1040,21 @@ function buildOpenCodeReplayPartTimelineEvent(params: {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isOpenCodeCompactionSummaryMessage(message: OpenCodeMessage): boolean {
|
||||||
|
return (
|
||||||
|
message.role === "assistant" &&
|
||||||
|
(message.summary === true || message.agent === "compaction" || message.mode === "compaction")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findOpenCodeCompactionPart(
|
||||||
|
message: OpenCodeSessionMessage,
|
||||||
|
): Extract<OpenCodePart, { type: "compaction" }> | undefined {
|
||||||
|
return message.parts.find(
|
||||||
|
(part): part is Extract<OpenCodePart, { type: "compaction" }> => part.type === "compaction",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async function readOpenCodeSessionMessagesFromSdk(
|
async function readOpenCodeSessionMessagesFromSdk(
|
||||||
client: Pick<OpencodeClient, "session">,
|
client: Pick<OpencodeClient, "session">,
|
||||||
session: OpenCodePersistedSession,
|
session: OpenCodePersistedSession,
|
||||||
@@ -1059,9 +1074,30 @@ async function readOpenCodeSessionMessagesFromSdk(
|
|||||||
function buildOpenCodeSessionTimeline(
|
function buildOpenCodeSessionTimeline(
|
||||||
messages: ReadonlyArray<OpenCodeSessionMessage>,
|
messages: ReadonlyArray<OpenCodeSessionMessage>,
|
||||||
): AgentTimelineItem[] {
|
): AgentTimelineItem[] {
|
||||||
return messages.flatMap((message) =>
|
const timeline: AgentTimelineItem[] = [];
|
||||||
buildOpenCodeReplayTimelineEvents(message).map((event) => event.item),
|
let hideNextAssistantAfterCompaction = false;
|
||||||
);
|
|
||||||
|
for (const message of messages) {
|
||||||
|
const compactionPart = findOpenCodeCompactionPart(message);
|
||||||
|
if (message.info.role === "assistant" && hideNextAssistantAfterCompaction) {
|
||||||
|
hideNextAssistantAfterCompaction = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (message.info.role === "user" && !compactionPart) {
|
||||||
|
hideNextAssistantAfterCompaction = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeline.push(...buildOpenCodeReplayTimelineEvents(message).map((event) => event.item));
|
||||||
|
|
||||||
|
if (message.info.role === "user" && compactionPart) {
|
||||||
|
timeline.push(
|
||||||
|
createCompactionTimelineItem("completed", compactionPart.auto ? "auto" : "manual"),
|
||||||
|
);
|
||||||
|
hideNextAssistantAfterCompaction = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return timeline;
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterOpenCodeRevertedMessages(
|
function filterOpenCodeRevertedMessages(
|
||||||
@@ -1120,6 +1156,9 @@ function buildOpenCodeReplayTimelineEvents(
|
|||||||
message: OpenCodeSessionMessage,
|
message: OpenCodeSessionMessage,
|
||||||
): Extract<AgentStreamEvent, { type: "timeline" }>[] {
|
): Extract<AgentStreamEvent, { type: "timeline" }>[] {
|
||||||
const { info, parts } = message;
|
const { info, parts } = message;
|
||||||
|
if (isOpenCodeCompactionSummaryMessage(info)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
if (info.role === "user") {
|
if (info.role === "user") {
|
||||||
const text = parts
|
const text = parts
|
||||||
.filter((part): part is Extract<OpenCodePart, { type: "text" }> => part.type === "text")
|
.filter((part): part is Extract<OpenCodePart, { type: "text" }> => part.type === "text")
|
||||||
@@ -1165,6 +1204,7 @@ function buildOpenCodeReplayTimelineEvents(
|
|||||||
|
|
||||||
export const __openCodeInternals = {
|
export const __openCodeInternals = {
|
||||||
buildOpenCodePromptParts,
|
buildOpenCodePromptParts,
|
||||||
|
buildOpenCodeSessionTimeline,
|
||||||
buildOpenCodeModelContextWindowLookup,
|
buildOpenCodeModelContextWindowLookup,
|
||||||
buildOpenCodeModelDefinition,
|
buildOpenCodeModelDefinition,
|
||||||
buildOpenCodeModelLookupKey,
|
buildOpenCodeModelLookupKey,
|
||||||
@@ -1591,6 +1631,9 @@ export interface OpenCodeEventTranslationState {
|
|||||||
sessionTotalCostUsd?: number;
|
sessionTotalCostUsd?: number;
|
||||||
streamedPartKeys: Set<string>;
|
streamedPartKeys: Set<string>;
|
||||||
emittedStructuredMessageIds: Set<string>;
|
emittedStructuredMessageIds: Set<string>;
|
||||||
|
compactionSummaryMessageIds: Set<string>;
|
||||||
|
emittedCompactionPartIds: Set<string>;
|
||||||
|
suppressAssistantMessagesUntilIdle?: { active: boolean };
|
||||||
/** Tracks the type of each part by ID, learned from message.part.updated events. */
|
/** Tracks the type of each part by ID, learned from message.part.updated events. */
|
||||||
partTypes: Map<string, string>;
|
partTypes: Map<string, string>;
|
||||||
subAgentsByCallId?: Map<string, OpenCodeSubAgentActivityState>;
|
subAgentsByCallId?: Map<string, OpenCodeSubAgentActivityState>;
|
||||||
@@ -1951,6 +1994,11 @@ export function translateOpenCodeEvent(
|
|||||||
function resetOpenCodeTurnTrackingState(state: OpenCodeEventTranslationState): void {
|
function resetOpenCodeTurnTrackingState(state: OpenCodeEventTranslationState): void {
|
||||||
state.streamedPartKeys.clear();
|
state.streamedPartKeys.clear();
|
||||||
state.partTypes.clear();
|
state.partTypes.clear();
|
||||||
|
state.compactionSummaryMessageIds.clear();
|
||||||
|
state.emittedCompactionPartIds.clear();
|
||||||
|
if (state.suppressAssistantMessagesUntilIdle) {
|
||||||
|
state.suppressAssistantMessagesUntilIdle.active = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOpenCodeSubAgentMaps(state: OpenCodeEventTranslationState): {
|
function getOpenCodeSubAgentMaps(state: OpenCodeEventTranslationState): {
|
||||||
@@ -2263,6 +2311,14 @@ function appendOpenCodeMessageUpdated(
|
|||||||
if (info.role !== "assistant") {
|
if (info.role !== "assistant") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (state.suppressAssistantMessagesUntilIdle?.active) {
|
||||||
|
state.compactionSummaryMessageIds.add(info.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isOpenCodeCompactionSummaryMessage(info)) {
|
||||||
|
state.compactionSummaryMessageIds.add(info.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const modelLookupKey = resolveOpenCodeModelLookupKeyFromAssistantMessage(info);
|
const modelLookupKey = resolveOpenCodeModelLookupKeyFromAssistantMessage(info);
|
||||||
if (modelLookupKey) {
|
if (modelLookupKey) {
|
||||||
const contextWindowMaxTokens = state.modelContextWindowsByModelKey?.get(modelLookupKey);
|
const contextWindowMaxTokens = state.modelContextWindowsByModelKey?.get(modelLookupKey);
|
||||||
@@ -2320,6 +2376,15 @@ function appendOpenCodeMessagePartUpdated(
|
|||||||
const messageRole = state.messageRoles.get(part.messageID);
|
const messageRole = state.messageRoles.get(part.messageID);
|
||||||
state.partTypes.set(part.id, part.type);
|
state.partTypes.set(part.id, part.type);
|
||||||
|
|
||||||
|
if (state.compactionSummaryMessageIds.has(part.messageID)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldSuppressOpenCodeAssistantPart(part, messageRole, state)) {
|
||||||
|
state.compactionSummaryMessageIds.add(part.messageID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (part.type === "text") {
|
if (part.type === "text") {
|
||||||
appendOpenCodeTextPart(part, messageRole, state, events);
|
appendOpenCodeTextPart(part, messageRole, state, events);
|
||||||
return;
|
return;
|
||||||
@@ -2336,6 +2401,10 @@ function appendOpenCodeMessagePartUpdated(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (part.type === "compaction") {
|
if (part.type === "compaction") {
|
||||||
|
if (state.emittedCompactionPartIds.has(part.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.emittedCompactionPartIds.add(part.id);
|
||||||
events.push({
|
events.push({
|
||||||
type: "timeline",
|
type: "timeline",
|
||||||
provider: "opencode",
|
provider: "opencode",
|
||||||
@@ -2361,6 +2430,18 @@ function appendOpenCodeMessagePartUpdated(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function shouldSuppressOpenCodeAssistantPart(
|
||||||
|
part: Extract<OpenCodeEvent, { type: "message.part.updated" }>["properties"]["part"],
|
||||||
|
messageRole: OpenCodeMessageRole | undefined,
|
||||||
|
state: OpenCodeEventTranslationState,
|
||||||
|
): boolean {
|
||||||
|
return (
|
||||||
|
state.suppressAssistantMessagesUntilIdle?.active === true &&
|
||||||
|
part.type === "text" &&
|
||||||
|
messageRole !== "user"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function appendOpenCodeTextPart(
|
function appendOpenCodeTextPart(
|
||||||
part: Extract<
|
part: Extract<
|
||||||
Extract<OpenCodeEvent, { type: "message.part.updated" }>["properties"]["part"],
|
Extract<OpenCodeEvent, { type: "message.part.updated" }>["properties"]["part"],
|
||||||
@@ -2691,6 +2772,9 @@ class OpenCodeAgentSession implements AgentSession {
|
|||||||
private streamedPartKeys = new Set<string>();
|
private streamedPartKeys = new Set<string>();
|
||||||
/** Tracks assistant messages already emitted from structured payloads. */
|
/** Tracks assistant messages already emitted from structured payloads. */
|
||||||
private emittedStructuredMessageIds = new Set<string>();
|
private emittedStructuredMessageIds = new Set<string>();
|
||||||
|
private compactionSummaryMessageIds = new Set<string>();
|
||||||
|
private emittedCompactionPartIds = new Set<string>();
|
||||||
|
private suppressAssistantMessagesUntilIdle = { active: false };
|
||||||
/** Tracks the type of each part by ID, learned from message.part.updated events. */
|
/** Tracks the type of each part by ID, learned from message.part.updated events. */
|
||||||
private partTypes = new Map<string, string>();
|
private partTypes = new Map<string, string>();
|
||||||
private availableModesCache: AgentMode[] | null = null;
|
private availableModesCache: AgentMode[] | null = null;
|
||||||
@@ -2874,6 +2958,7 @@ class OpenCodeAgentSession implements AgentSession {
|
|||||||
|
|
||||||
const parts = buildOpenCodePromptParts(prompt);
|
const parts = buildOpenCodePromptParts(prompt);
|
||||||
this.pendingUserMessageText = buildOpenCodeUserTimelineText(prompt);
|
this.pendingUserMessageText = buildOpenCodeUserTimelineText(prompt);
|
||||||
|
this.suppressAssistantMessagesUntilIdle.active = false;
|
||||||
const model = this.parseModel(this.config.model);
|
const model = this.parseModel(this.config.model);
|
||||||
const thinkingOptionId = this.config.thinkingOptionId;
|
const thinkingOptionId = this.config.thinkingOptionId;
|
||||||
const effectiveVariant = thinkingOptionId ?? undefined;
|
const effectiveVariant = thinkingOptionId ?? undefined;
|
||||||
@@ -2895,6 +2980,7 @@ class OpenCodeAgentSession implements AgentSession {
|
|||||||
const slashCommand = await this.resolveSlashCommandInvocation(prompt);
|
const slashCommand = await this.resolveSlashCommandInvocation(prompt);
|
||||||
if (slashCommand) {
|
if (slashCommand) {
|
||||||
if (slashCommand.commandName === "compact" || slashCommand.commandName === "summarize") {
|
if (slashCommand.commandName === "compact" || slashCommand.commandName === "summarize") {
|
||||||
|
this.suppressAssistantMessagesUntilIdle.active = true;
|
||||||
void this.client.session
|
void this.client.session
|
||||||
.summarize({
|
.summarize({
|
||||||
sessionID: this.sessionId,
|
sessionID: this.sessionId,
|
||||||
@@ -2903,6 +2989,7 @@ class OpenCodeAgentSession implements AgentSession {
|
|||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
|
this.suppressAssistantMessagesUntilIdle.active = false;
|
||||||
this.finishForegroundTurn(
|
this.finishForegroundTurn(
|
||||||
{
|
{
|
||||||
type: "turn_failed",
|
type: "turn_failed",
|
||||||
@@ -2911,15 +2998,11 @@ class OpenCodeAgentSession implements AgentSession {
|
|||||||
},
|
},
|
||||||
turnId,
|
turnId,
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
this.finishForegroundTurn(
|
|
||||||
{ type: "turn_completed", provider: "opencode", usage: undefined },
|
|
||||||
turnId,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
this.suppressAssistantMessagesUntilIdle.active = false;
|
||||||
this.finishForegroundTurn(
|
this.finishForegroundTurn(
|
||||||
{
|
{
|
||||||
type: "turn_failed",
|
type: "turn_failed",
|
||||||
@@ -3640,6 +3723,9 @@ class OpenCodeAgentSession implements AgentSession {
|
|||||||
sessionTotalCostUsd: this.sessionTotalCostUsd,
|
sessionTotalCostUsd: this.sessionTotalCostUsd,
|
||||||
streamedPartKeys: this.streamedPartKeys,
|
streamedPartKeys: this.streamedPartKeys,
|
||||||
emittedStructuredMessageIds: this.emittedStructuredMessageIds,
|
emittedStructuredMessageIds: this.emittedStructuredMessageIds,
|
||||||
|
compactionSummaryMessageIds: this.compactionSummaryMessageIds,
|
||||||
|
emittedCompactionPartIds: this.emittedCompactionPartIds,
|
||||||
|
suppressAssistantMessagesUntilIdle: this.suppressAssistantMessagesUntilIdle,
|
||||||
partTypes: this.partTypes,
|
partTypes: this.partTypes,
|
||||||
subAgentsByCallId: this.subAgentsByCallId,
|
subAgentsByCallId: this.subAgentsByCallId,
|
||||||
subAgentCallIdByChildSessionId: this.subAgentCallIdByChildSessionId,
|
subAgentCallIdByChildSessionId: this.subAgentCallIdByChildSessionId,
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ function createState(sessionId = "session-1"): OpenCodeEventTranslationState {
|
|||||||
accumulatedUsage: {},
|
accumulatedUsage: {},
|
||||||
streamedPartKeys: new Set(),
|
streamedPartKeys: new Set(),
|
||||||
emittedStructuredMessageIds: new Set(),
|
emittedStructuredMessageIds: new Set(),
|
||||||
|
compactionSummaryMessageIds: new Set(),
|
||||||
|
emittedCompactionPartIds: new Set(),
|
||||||
partTypes: new Map(),
|
partTypes: new Map(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -801,6 +803,107 @@ describe("translateOpenCodeEvent", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not render OpenCode compaction summaries as assistant messages", () => {
|
||||||
|
const state = createState();
|
||||||
|
|
||||||
|
const loading = translateOpenCodeEvent(
|
||||||
|
{
|
||||||
|
type: "message.part.updated",
|
||||||
|
properties: {
|
||||||
|
part: {
|
||||||
|
id: "compaction-part-1",
|
||||||
|
sessionID: "session-1",
|
||||||
|
messageID: "message-compaction-user-1",
|
||||||
|
type: "compaction",
|
||||||
|
auto: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
state,
|
||||||
|
);
|
||||||
|
|
||||||
|
translateOpenCodeEvent(
|
||||||
|
{
|
||||||
|
type: "message.updated",
|
||||||
|
properties: {
|
||||||
|
info: {
|
||||||
|
id: "message-compaction-summary-1",
|
||||||
|
sessionID: "session-1",
|
||||||
|
role: "assistant",
|
||||||
|
mode: "compaction",
|
||||||
|
agent: "compaction",
|
||||||
|
summary: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
state,
|
||||||
|
);
|
||||||
|
|
||||||
|
const summaryText = translateOpenCodeEvent(
|
||||||
|
{
|
||||||
|
type: "message.part.updated",
|
||||||
|
properties: {
|
||||||
|
part: {
|
||||||
|
id: "summary-text-part-1",
|
||||||
|
sessionID: "session-1",
|
||||||
|
messageID: "message-compaction-summary-1",
|
||||||
|
type: "text",
|
||||||
|
text: "## Goal\n- Preserve context while continuing the task.",
|
||||||
|
time: { start: 1, end: 2 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
state,
|
||||||
|
);
|
||||||
|
|
||||||
|
const duplicateLoading = translateOpenCodeEvent(
|
||||||
|
{
|
||||||
|
type: "message.part.updated",
|
||||||
|
properties: {
|
||||||
|
part: {
|
||||||
|
id: "compaction-part-1",
|
||||||
|
sessionID: "session-1",
|
||||||
|
messageID: "message-compaction-user-1",
|
||||||
|
type: "compaction",
|
||||||
|
auto: true,
|
||||||
|
tail_start_id: "message-tail-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
state,
|
||||||
|
);
|
||||||
|
|
||||||
|
const completed = translateOpenCodeEvent(
|
||||||
|
{
|
||||||
|
type: "session.compacted",
|
||||||
|
properties: {
|
||||||
|
sessionID: "session-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
state,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect([...loading, ...summaryText, ...duplicateLoading, ...completed]).toEqual([
|
||||||
|
{
|
||||||
|
type: "timeline",
|
||||||
|
provider: "opencode",
|
||||||
|
item: {
|
||||||
|
type: "compaction",
|
||||||
|
status: "loading",
|
||||||
|
trigger: "auto",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "timeline",
|
||||||
|
provider: "opencode",
|
||||||
|
item: {
|
||||||
|
type: "compaction",
|
||||||
|
status: "completed",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it("emits compaction completed timeline items from session.compacted", () => {
|
it("emits compaction completed timeline items from session.compacted", () => {
|
||||||
const state = createState();
|
const state = createState();
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -1,4 +1,4 @@
|
|||||||
import type { OpencodeClient } from "@opencode-ai/sdk/v2/client";
|
import type { Event as OpenCodeEvent, OpencodeClient } from "@opencode-ai/sdk/v2/client";
|
||||||
|
|
||||||
import type { OpenCodeRuntime, OpenCodeServerAcquisition } from "../runtime.js";
|
import type { OpenCodeRuntime, OpenCodeServerAcquisition } from "../runtime.js";
|
||||||
|
|
||||||
@@ -92,6 +92,7 @@ export class TestOpenCodeClient {
|
|||||||
sessionMessagesResponse: OpenCodeResponse = { data: [] };
|
sessionMessagesResponse: OpenCodeResponse = { data: [] };
|
||||||
sessionPromptAsyncEvents: unknown[] = [idleEvent()];
|
sessionPromptAsyncEvents: unknown[] = [idleEvent()];
|
||||||
sessionPromptAsyncResponse: OpenCodeResponse = {};
|
sessionPromptAsyncResponse: OpenCodeResponse = {};
|
||||||
|
sessionSummarizeEvents: OpenCodeEvent[] = [];
|
||||||
sessionSummarizeResponse: OpenCodeResponse = { data: {} };
|
sessionSummarizeResponse: OpenCodeResponse = { data: {} };
|
||||||
sessionUpdateResponse: OpenCodeResponse = {};
|
sessionUpdateResponse: OpenCodeResponse = {};
|
||||||
private readonly queuedEventStream = createQueuedEventStream();
|
private readonly queuedEventStream = createQueuedEventStream();
|
||||||
@@ -208,6 +209,9 @@ export class TestOpenCodeClient {
|
|||||||
},
|
},
|
||||||
summarize: async (parameters: unknown) => {
|
summarize: async (parameters: unknown) => {
|
||||||
this.calls.sessionSummarize.push(parameters);
|
this.calls.sessionSummarize.push(parameters);
|
||||||
|
for (const event of this.sessionSummarizeEvents) {
|
||||||
|
this.emitEvent(event);
|
||||||
|
}
|
||||||
return this.sessionSummarizeResponse;
|
return this.sessionSummarizeResponse;
|
||||||
},
|
},
|
||||||
update: async (parameters: unknown) => {
|
update: async (parameters: unknown) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user