Skip to content

Commit ebc41da

Browse files
committed
feat(provider): systemPrompt modes (rules|message|omit) in transcript mapping
1 parent 7d16e5b commit ebc41da

2 files changed

Lines changed: 40 additions & 3 deletions

File tree

‎src/provider/message-map.ts‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
import type { LanguageModelV3Prompt } from "@ai-sdk/provider";
22
import type { SDKImage, SDKUserMessage } from "@cursor/sdk";
33

4+
/**
5+
* How opencode's system prompt reaches the Cursor agent.
6+
* - "rules" (default): delivered out-of-band as a Cursor project rule
7+
* (see system-rule.ts) and therefore omitted from this flattened transcript.
8+
* - "message": legacy — inlined as a `# System` block in the transcript.
9+
* Injection-hardened Cursor models may reject this; kept for back-compat.
10+
* - "omit": dropped entirely (no rule, no inline).
11+
*/
12+
export type SystemPromptMode = "rules" | "message" | "omit";
13+
414
/**
515
* Caps on inlined tool payloads in the flattened transcript. Tool outputs are
616
* INCLUDED (truncated) rather than dropped so a fresh/diverged agent still sees
@@ -37,6 +47,7 @@ function truncate(text: string, cap: number): string {
3747
*/
3848
export function promptToCursorMessage(
3949
prompt: LanguageModelV3Prompt,
50+
systemPrompt: SystemPromptMode = "rules",
4051
): SDKUserMessage {
4152
const lines: string[] = [];
4253
const images: SDKImage[] = [];
@@ -45,7 +56,12 @@ export function promptToCursorMessage(
4556
const isLast = index === prompt.length - 1;
4657
switch (message.role) {
4758
case "system":
48-
lines.push(`# System\n${message.content}`);
59+
// Only inline in legacy "message" mode. In "rules" mode the system
60+
// prompt is delivered via .cursor/rules (authoritative, not flagged);
61+
// in "omit" mode it is dropped.
62+
if (systemPrompt === "message") {
63+
lines.push(`# System\n${message.content}`);
64+
}
4965
break;
5066
case "user": {
5167
const text: string[] = [];

‎test/message-map.test.ts‎

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,42 @@ import {
66
} from "../src/provider/message-map.js";
77

88
describe("promptToCursorMessage", () => {
9-
it("flattens a multi-role conversation into a transcript", () => {
9+
it("omits the system prompt from the transcript by default (rules mode)", () => {
1010
const prompt: LanguageModelV3Prompt = [
1111
{ role: "system", content: "Be concise." },
1212
{ role: "user", content: [{ type: "text", text: "Hello" }] },
1313
{ role: "assistant", content: [{ type: "text", text: "Hi there" }] },
1414
{ role: "user", content: [{ type: "text", text: "What is 2+2?" }] },
1515
];
1616
const msg = promptToCursorMessage(prompt);
17-
expect(msg.text).toContain("# System\nBe concise.");
17+
// System prompt is delivered via the Cursor rules channel, not inline.
18+
expect(msg.text).not.toContain("# System");
19+
expect(msg.text).not.toContain("Be concise.");
1820
expect(msg.text).toContain("# User\nHello");
1921
expect(msg.text).toContain("# Assistant\nHi there");
2022
expect(msg.text).toContain("# User\nWhat is 2+2?");
2123
expect(msg.images).toBeUndefined();
2224
});
2325

26+
it("includes the system prompt inline in 'message' (legacy) mode", () => {
27+
const prompt: LanguageModelV3Prompt = [
28+
{ role: "system", content: "Be concise." },
29+
{ role: "user", content: [{ type: "text", text: "Hi" }] },
30+
];
31+
const msg = promptToCursorMessage(prompt, "message");
32+
expect(msg.text).toContain("# System\nBe concise.");
33+
});
34+
35+
it("omits the system prompt in 'omit' mode", () => {
36+
const prompt: LanguageModelV3Prompt = [
37+
{ role: "system", content: "Be concise." },
38+
{ role: "user", content: [{ type: "text", text: "Hi" }] },
39+
];
40+
const msg = promptToCursorMessage(prompt, "omit");
41+
expect(msg.text).not.toContain("Be concise.");
42+
expect(msg.text).toBe("# User\nHi");
43+
});
44+
2445
it("attaches images from the final user turn as base64 data", () => {
2546
const bytes = new Uint8Array([1, 2, 3, 4]);
2647
const prompt: LanguageModelV3Prompt = [

0 commit comments

Comments
 (0)