fix(omp): honor hidden custom messages (#2280)
This commit is contained in:
@@ -260,6 +260,23 @@ describe("OMP agent client and session", () => {
|
|||||||
expect(omp.completedTurnCount()).toBe(1);
|
expect(omp.completedTurnCount()).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("omits live custom messages when display is false", async () => {
|
||||||
|
const omp = new OmpHarness();
|
||||||
|
await omp.start();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
omp.runPromptAfterExtensionNotice("hello OMP", "model turn completed", false),
|
||||||
|
).resolves.toMatchObject({ finalText: expect.stringContaining("model turn completed") });
|
||||||
|
expect(omp.timeline()).toEqual([
|
||||||
|
{ type: "user_message", text: "hello OMP", messageId: "user-1" },
|
||||||
|
{
|
||||||
|
type: "assistant_message",
|
||||||
|
text: "model turn completed",
|
||||||
|
messageId: "omp-assistant-1",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
test("does not complete a queued model turn from OMP's local-only hint", async () => {
|
test("does not complete a queued model turn from OMP's local-only hint", async () => {
|
||||||
const omp = new OmpHarness();
|
const omp = new OmpHarness();
|
||||||
await omp.start();
|
await omp.start();
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ import {
|
|||||||
} from "./provider-config.js";
|
} from "./provider-config.js";
|
||||||
export { formatOmpVersionSupport, resolveOmpDiagnosticPaths } from "./provider-config.js";
|
export { formatOmpVersionSupport, resolveOmpDiagnosticPaths } from "./provider-config.js";
|
||||||
import { OmpSubagentCardTracker, type OmpSubagentCardScheduler } from "./subagent-card-tracker.js";
|
import { OmpSubagentCardTracker, type OmpSubagentCardScheduler } from "./subagent-card-tracker.js";
|
||||||
|
import { shouldDisplayOmpCustomMessage } from "./custom-message.js";
|
||||||
import { getUserMessageText } from "./message-history.js";
|
import { getUserMessageText } from "./message-history.js";
|
||||||
import { materializeProviderImage } from "../provider-image-output.js";
|
import { materializeProviderImage } from "../provider-image-output.js";
|
||||||
import { OmpCliRuntime } from "./cli-runtime.js";
|
import { OmpCliRuntime } from "./cli-runtime.js";
|
||||||
@@ -2064,15 +2065,17 @@ export class OmpAgentSession implements AgentSession {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event.message.role === "custom") {
|
if (event.message.role === "custom") {
|
||||||
const text = getUserMessageText(event.message.content);
|
if (shouldDisplayOmpCustomMessage(event.message)) {
|
||||||
if (text) {
|
const text = getUserMessageText(event.message.content);
|
||||||
const advisorItem = mapOmpAdvisorMessageToToolCall(event.message, text);
|
if (text) {
|
||||||
this.emit({
|
const advisorItem = mapOmpAdvisorMessageToToolCall(event.message, text);
|
||||||
type: "timeline",
|
this.emit({
|
||||||
provider: this.provider,
|
type: "timeline",
|
||||||
turnId,
|
provider: this.provider,
|
||||||
item: advisorItem ?? { type: "assistant_message", text },
|
turnId,
|
||||||
});
|
item: advisorItem ?? { type: "assistant_message", text },
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!this.activeTurnHasUserMessage) {
|
if (!this.activeTurnHasUserMessage) {
|
||||||
this.completeTurn(turnId, []);
|
this.completeTurn(turnId, []);
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import type { OmpAgentMessage } from "./rpc-types.js";
|
||||||
|
|
||||||
|
type OmpCustomMessage = Extract<OmpAgentMessage, { role: "custom" }>;
|
||||||
|
|
||||||
|
export function shouldDisplayOmpCustomMessage(message: OmpCustomMessage): boolean {
|
||||||
|
return Reflect.get(message, "display") !== false;
|
||||||
|
}
|
||||||
@@ -209,6 +209,54 @@ describe("OMP history mapper", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("omits replayed custom messages only when display is false", async () => {
|
||||||
|
await expect(
|
||||||
|
collectHistory(
|
||||||
|
[
|
||||||
|
{ role: "user", content: "first prompt" },
|
||||||
|
{ role: "custom", content: "hidden reminder", display: false },
|
||||||
|
{ role: "custom", content: "visible explicit custom", display: true },
|
||||||
|
{ role: "custom", content: "visible legacy custom" },
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
content: [{ type: "text", text: "assistant reply" }],
|
||||||
|
responseId: "assistant-history",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[{ id: "entry-user-1", text: "first prompt" }],
|
||||||
|
),
|
||||||
|
).resolves.toEqual([
|
||||||
|
{
|
||||||
|
type: "timeline",
|
||||||
|
provider: "omp",
|
||||||
|
item: {
|
||||||
|
type: "user_message",
|
||||||
|
text: "first prompt",
|
||||||
|
messageId: "entry-user-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "timeline",
|
||||||
|
provider: "omp",
|
||||||
|
item: { type: "assistant_message", text: "visible explicit custom" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "timeline",
|
||||||
|
provider: "omp",
|
||||||
|
item: { type: "assistant_message", text: "visible legacy custom" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "timeline",
|
||||||
|
provider: "omp",
|
||||||
|
item: {
|
||||||
|
type: "assistant_message",
|
||||||
|
text: "assistant reply",
|
||||||
|
messageId: "assistant-history",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
test("suppresses replayed raw todo tool calls through the OMP detail hook", async () => {
|
test("suppresses replayed raw todo tool calls through the OMP detail hook", async () => {
|
||||||
await expect(
|
await expect(
|
||||||
collectHistory([
|
collectHistory([
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { AgentStreamEvent, AgentTimelineItem, ToolCallDetail } from "../../agent-sdk-types.js";
|
import type { AgentStreamEvent, AgentTimelineItem, ToolCallDetail } from "../../agent-sdk-types.js";
|
||||||
import type { OmpAgentMessage, OmpImageContent, OmpTextContent } from "./rpc-types.js";
|
import type { OmpAgentMessage, OmpImageContent, OmpTextContent } from "./rpc-types.js";
|
||||||
|
import { shouldDisplayOmpCustomMessage } from "./custom-message.js";
|
||||||
import {
|
import {
|
||||||
extractTextFromToolResult,
|
extractTextFromToolResult,
|
||||||
mapToolDetail,
|
mapToolDetail,
|
||||||
@@ -117,6 +118,9 @@ export class OmpHistoryMapper {
|
|||||||
private mapCustomMessage(
|
private mapCustomMessage(
|
||||||
message: Extract<OmpAgentMessage, { role: "custom" }>,
|
message: Extract<OmpAgentMessage, { role: "custom" }>,
|
||||||
): AgentStreamEvent[] {
|
): AgentStreamEvent[] {
|
||||||
|
if (!shouldDisplayOmpCustomMessage(message)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
const text = getUserMessageText(message.content);
|
const text = getUserMessageText(message.content);
|
||||||
const mappedEvent = text ? this.hooks.mapCustomMessage?.(message, text, this.provider) : null;
|
const mappedEvent = text ? this.hooks.mapCustomMessage?.(message, text, this.provider) : null;
|
||||||
if (mappedEvent) {
|
if (mappedEvent) {
|
||||||
|
|||||||
@@ -201,16 +201,25 @@ export class OmpHarness {
|
|||||||
return { completion };
|
return { completion };
|
||||||
}
|
}
|
||||||
|
|
||||||
async runPromptAfterExtensionNotice(input: string, output: string): Promise<unknown> {
|
async runPromptAfterExtensionNotice(
|
||||||
|
input: string,
|
||||||
|
output: string,
|
||||||
|
display?: boolean,
|
||||||
|
): Promise<unknown> {
|
||||||
const session = this.requireSession();
|
const session = this.requireSession();
|
||||||
const promptStarted = this.omp.latestSession().nextPrompt();
|
const promptStarted = this.omp.latestSession().nextPrompt();
|
||||||
const run = session.run(input);
|
const run = session.run(input);
|
||||||
await promptStarted;
|
await promptStarted;
|
||||||
const runtime = this.omp.latestSession();
|
const runtime = this.omp.latestSession();
|
||||||
|
const message = {
|
||||||
|
role: "custom" as const,
|
||||||
|
content: "extension inventory changed",
|
||||||
|
...(display === undefined ? {} : { display }),
|
||||||
|
};
|
||||||
runtime.beginTurn();
|
runtime.beginTurn();
|
||||||
runtime.acceptPrompt(input, "user-1");
|
runtime.acceptPrompt(input, "user-1");
|
||||||
runtime.acceptCustomMessage("extension inventory changed");
|
runtime.emit({ type: "message_end", message });
|
||||||
runtime.finishTurn({ role: "custom", content: "extension inventory changed" });
|
runtime.finishTurn(message);
|
||||||
runtime.beginTurn();
|
runtime.beginTurn();
|
||||||
runtime.streamAssistantText(output);
|
runtime.streamAssistantText(output);
|
||||||
runtime.finishTurn();
|
runtime.finishTurn();
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { describe, expect, test } from "vitest";
|
import { describe, expect, test } from "vitest";
|
||||||
|
|
||||||
import { parseToolArgs, parseToolResult } from "./tool-call-detail.js";
|
import { parseToolArgs, parseToolResult } from "./tool-call-detail.js";
|
||||||
import { OmpAvailableCommandsUpdateEventSchema } from "./rpc-types.js";
|
import { OmpAgentMessageSchema, OmpAvailableCommandsUpdateEventSchema } from "./rpc-types.js";
|
||||||
import { mapOmpToolDetail } from "./tool-call-mapper.js";
|
import { mapOmpToolDetail } from "./tool-call-mapper.js";
|
||||||
|
import { shouldDisplayOmpCustomMessage } from "./custom-message.js";
|
||||||
|
|
||||||
describe("OMP 17 RPC compatibility", () => {
|
describe("OMP 17 RPC compatibility", () => {
|
||||||
test("parses source-attributed command updates", () => {
|
test("parses source-attributed command updates", () => {
|
||||||
@@ -16,6 +17,20 @@ describe("OMP 17 RPC compatibility", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("keeps non-false custom display metadata backward compatible", () => {
|
||||||
|
const message = OmpAgentMessageSchema.parse({
|
||||||
|
role: "custom",
|
||||||
|
content: "visible custom message",
|
||||||
|
display: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(message).toMatchObject({ display: null });
|
||||||
|
if (message.role !== "custom") {
|
||||||
|
throw new Error("Expected a custom OMP message");
|
||||||
|
}
|
||||||
|
expect(shouldDisplayOmpCustomMessage(message)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
test("maps subscribed custom tool events without assuming built-in names", () => {
|
test("maps subscribed custom tool events without assuming built-in names", () => {
|
||||||
const event = {
|
const event = {
|
||||||
type: "tool_execution_start",
|
type: "tool_execution_start",
|
||||||
|
|||||||
Reference in New Issue
Block a user