Merge pull request #48 from warc0s/fix/omp-developer-messages
fix(core): preserve developer messages
This commit is contained in:
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- Stop silently dropping `role: "developer"` messages (for example OMP advisor steering notes, reminders, and nudges). `/alpha/generate` only accepts `user`, `assistant`, and `tool` roles, so developer messages are now forwarded as `user` messages with identical content in the same chronological position instead of disappearing from the request.
|
||||||
|
|
||||||
## 0.6.0 - 2026-08-25
|
## 0.6.0 - 2026-08-25
|
||||||
|
|
||||||
- Allow switching from a vision-capable model to a text-only model by omitting historical image tool results while preserving their text output; direct image prompts still fail clearly.
|
- Allow switching from a vision-capable model to a text-only model by omitting historical image tool results while preserving their text output; direct image prompts still fail clearly.
|
||||||
|
|||||||
Generated
+2
-2
@@ -473,7 +473,7 @@
|
|||||||
"version": "25.6.0",
|
"version": "25.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz",
|
||||||
"integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
|
"integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.19.0"
|
"undici-types": "~7.19.0"
|
||||||
@@ -589,7 +589,7 @@
|
|||||||
"version": "7.19.2",
|
"version": "7.19.2",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz",
|
||||||
"integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
|
"integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -229,7 +229,12 @@ export function messagesToCC(
|
|||||||
const { callIds, resultIds } = toolCallState(messages)
|
const { callIds, resultIds } = toolCallState(messages)
|
||||||
|
|
||||||
for (const message of messages ?? []) {
|
for (const message of messages ?? []) {
|
||||||
if (message.role === "user") {
|
if (message.role === "user" || message.role === "developer") {
|
||||||
|
// Hosts such as OMP steer the agent by injecting developer-role messages
|
||||||
|
// (advisor notes, reminders, nudges) mid-conversation. /alpha/generate
|
||||||
|
// only accepts user, assistant, and tool roles, so degrade the role to
|
||||||
|
// user instead of dropping the message. Content and chronological
|
||||||
|
// position are preserved; system-prompt hoisting would change semantics.
|
||||||
out.push({
|
out.push({
|
||||||
role: "user",
|
role: "user",
|
||||||
content: userContentToCommandCode(message.content, allowImages),
|
content: userContentToCommandCode(message.content, allowImages),
|
||||||
|
|||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Minimal OMP extension used by tests/test-omp-compat.mjs.
|
||||||
|
*
|
||||||
|
* Appends a custom advisor message to the session on session start, mirroring
|
||||||
|
* how the OMP Advisor runtime injects steering notes. OMP converts custom
|
||||||
|
* messages to `role: "developer"` LLM messages before handing them to the
|
||||||
|
* provider. Types are declared inline so the fixture has no runtime or
|
||||||
|
* typecheck dependency on OMP packages.
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface AdvisorInjectorSendMessage {
|
||||||
|
(
|
||||||
|
message: {
|
||||||
|
customType: string
|
||||||
|
content: string
|
||||||
|
display: boolean
|
||||||
|
attribution: string
|
||||||
|
},
|
||||||
|
options?: { triggerTurn?: boolean },
|
||||||
|
): void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AdvisorInjectorApi {
|
||||||
|
on: (event: "session_start", handler: () => void | Promise<void>) => void
|
||||||
|
sendMessage: AdvisorInjectorSendMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function advisoryInjectorExtension(pi: AdvisorInjectorApi): void {
|
||||||
|
pi.on("session_start", async () => {
|
||||||
|
pi.sendMessage(
|
||||||
|
{
|
||||||
|
customType: "advisor",
|
||||||
|
content:
|
||||||
|
'<advisory severity="blocker" guidance="weigh, don\'t blindly obey">\nStop and correct the benchmark.\n</advisory>',
|
||||||
|
display: true,
|
||||||
|
attribution: "agent",
|
||||||
|
},
|
||||||
|
{ triggerTurn: false },
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
+124
-14
@@ -19,7 +19,10 @@ import { fileURLToPath } from "node:url"
|
|||||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||||
const PROJECT_DIR = resolve(__dirname, "..")
|
const PROJECT_DIR = resolve(__dirname, "..")
|
||||||
const EXT_PATH = resolve(PROJECT_DIR, "index.ts")
|
const EXT_PATH = resolve(PROJECT_DIR, "index.ts")
|
||||||
|
const ADVISORY_EXT_PATH = resolve(PROJECT_DIR, "tests/fixtures/advisory-injector-extension.ts")
|
||||||
const TEST_MODEL = "deepseek/deepseek-v4-flash"
|
const TEST_MODEL = "deepseek/deepseek-v4-flash"
|
||||||
|
const ADVISORY_XML =
|
||||||
|
'<advisory severity="blocker" guidance="weigh, don\'t blindly obey">\nStop and correct the benchmark.\n</advisory>'
|
||||||
|
|
||||||
function findOmpBinary() {
|
function findOmpBinary() {
|
||||||
if (process.env.OMP_BIN) return process.env.OMP_BIN
|
if (process.env.OMP_BIN) return process.env.OMP_BIN
|
||||||
@@ -45,7 +48,11 @@ const tempHome = mkdtempSync(join(tmpdir(), "omp-cc-home-"))
|
|||||||
let requestCount = 0
|
let requestCount = 0
|
||||||
let modelListRequestCount = 0
|
let modelListRequestCount = 0
|
||||||
let lastRequestBody
|
let lastRequestBody
|
||||||
|
let requestBodies = []
|
||||||
let lastRequestHeaders = {}
|
let lastRequestHeaders = {}
|
||||||
|
// When true the mock Provider API answers 403 upgrade_required so the
|
||||||
|
// transport router falls back to the legacy /alpha/generate transport.
|
||||||
|
let providerUpgradeRequired = false
|
||||||
|
|
||||||
const server = createServer((req, res) => {
|
const server = createServer((req, res) => {
|
||||||
if (req.method === "GET" && req.url === "/provider/v1/models") {
|
if (req.method === "GET" && req.url === "/provider/v1/models") {
|
||||||
@@ -77,7 +84,52 @@ const server = createServer((req, res) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.method !== "POST" || req.url !== "/provider/v1/chat/completions") {
|
if (req.method === "POST" && req.url === "/provider/v1/chat/completions") {
|
||||||
|
requestCount += 1
|
||||||
|
lastRequestHeaders = Object.fromEntries(
|
||||||
|
Object.entries(req.headers).map(([key, value]) => [
|
||||||
|
key,
|
||||||
|
Array.isArray(value) ? value.join(", ") : (value ?? ""),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
|
||||||
|
let body = ""
|
||||||
|
req.on("data", (chunk) => {
|
||||||
|
body += chunk.toString("utf-8")
|
||||||
|
})
|
||||||
|
req.on("end", () => {
|
||||||
|
try {
|
||||||
|
lastRequestBody = JSON.parse(body)
|
||||||
|
requestBodies.push(lastRequestBody)
|
||||||
|
} catch {
|
||||||
|
lastRequestBody = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
if (providerUpgradeRequired) {
|
||||||
|
res.writeHead(403, { "Content-Type": "application/json; charset=utf-8" })
|
||||||
|
res.end(JSON.stringify({ error: { code: "upgrade_required" } }))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res.writeHead(200, {
|
||||||
|
"Content-Type": "text/event-stream; charset=utf-8",
|
||||||
|
"Transfer-Encoding": "chunked",
|
||||||
|
})
|
||||||
|
res.write(
|
||||||
|
`data: ${JSON.stringify({ id: "mock", object: "chat.completion.chunk", choices: [{ index: 0, delta: { role: "assistant", content: "mock-omp-ok" }, finish_reason: null }] })}\n\n`,
|
||||||
|
)
|
||||||
|
res.write(
|
||||||
|
`data: ${JSON.stringify({ id: "mock", object: "chat.completion.chunk", choices: [{ index: 0, delta: {}, finish_reason: "stop" }] })}\n\n`,
|
||||||
|
)
|
||||||
|
res.write(
|
||||||
|
`data: ${JSON.stringify({ id: "mock", object: "chat.completion.chunk", choices: [], usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 } })}\n\n`,
|
||||||
|
)
|
||||||
|
res.end("data: [DONE]\n\n")
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.method !== "POST" || req.url !== "/alpha/generate") {
|
||||||
res.writeHead(404)
|
res.writeHead(404)
|
||||||
res.end("Not found")
|
res.end("Not found")
|
||||||
return
|
return
|
||||||
@@ -91,31 +143,27 @@ const server = createServer((req, res) => {
|
|||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
|
|
||||||
let body = ""
|
let generateBody = ""
|
||||||
req.on("data", (chunk) => {
|
req.on("data", (chunk) => {
|
||||||
body += chunk.toString("utf-8")
|
generateBody += chunk.toString("utf-8")
|
||||||
})
|
})
|
||||||
req.on("end", () => {
|
req.on("end", () => {
|
||||||
try {
|
try {
|
||||||
lastRequestBody = JSON.parse(body)
|
lastRequestBody = JSON.parse(generateBody)
|
||||||
|
requestBodies.push(lastRequestBody)
|
||||||
} catch {
|
} catch {
|
||||||
lastRequestBody = undefined
|
lastRequestBody = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
res.writeHead(200, {
|
res.writeHead(200, {
|
||||||
"Content-Type": "text/event-stream; charset=utf-8",
|
"Content-Type": "text/plain; charset=utf-8",
|
||||||
"Transfer-Encoding": "chunked",
|
"Transfer-Encoding": "chunked",
|
||||||
})
|
})
|
||||||
|
res.write(`${JSON.stringify({ type: "text-delta", text: "mock-omp-ok" })}\n`)
|
||||||
res.write(
|
res.write(
|
||||||
`data: ${JSON.stringify({ id: "mock", object: "chat.completion.chunk", choices: [{ index: 0, delta: { role: "assistant", content: "mock-omp-ok" }, finish_reason: null }] })}\n\n`,
|
`${JSON.stringify({ type: "finish", finishReason: "stop", totalUsage: { inputTokens: 1, outputTokens: 1 } })}\n`,
|
||||||
)
|
)
|
||||||
res.write(
|
res.end()
|
||||||
`data: ${JSON.stringify({ id: "mock", object: "chat.completion.chunk", choices: [{ index: 0, delta: {}, finish_reason: "stop" }] })}\n\n`,
|
|
||||||
)
|
|
||||||
res.write(
|
|
||||||
`data: ${JSON.stringify({ id: "mock", object: "chat.completion.chunk", choices: [], usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 } })}\n\n`,
|
|
||||||
)
|
|
||||||
res.end("data: [DONE]\n\n")
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -176,7 +224,10 @@ try {
|
|||||||
const listOutput = result.stdout || result.stderr
|
const listOutput = result.stdout || result.stderr
|
||||||
assert.match(listOutput, /commandcode/)
|
assert.match(listOutput, /commandcode/)
|
||||||
assert.match(listOutput, /deepseek\/deepseek-v4-flash/)
|
assert.match(listOutput, /deepseek\/deepseek-v4-flash/)
|
||||||
assert.equal(modelListRequestCount, 1)
|
// The failed flag attempt may already load the extension and fetch the
|
||||||
|
// catalog once before the subcommand fallback runs, so only assert that
|
||||||
|
// the mock catalog was actually consulted.
|
||||||
|
assert.ok(modelListRequestCount >= 1)
|
||||||
assert.doesNotThrow(() =>
|
assert.doesNotThrow(() =>
|
||||||
accessSync(join(tempHome, ".omp", "agent", "commandcode-models.json"), constants.R_OK),
|
accessSync(join(tempHome, ".omp", "agent", "commandcode-models.json"), constants.R_OK),
|
||||||
)
|
)
|
||||||
@@ -184,6 +235,7 @@ try {
|
|||||||
|
|
||||||
console.log("[omp-compat] print mode through real extension and mock API")
|
console.log("[omp-compat] print mode through real extension and mock API")
|
||||||
requestCount = 0
|
requestCount = 0
|
||||||
|
requestBodies = []
|
||||||
const print = await runOmp(
|
const print = await runOmp(
|
||||||
["-e", EXT_PATH, "-p", "say mock token", "--model", `commandcode/${TEST_MODEL}`],
|
["-e", EXT_PATH, "-p", "say mock token", "--model", `commandcode/${TEST_MODEL}`],
|
||||||
30_000,
|
30_000,
|
||||||
@@ -199,6 +251,64 @@ try {
|
|||||||
assert.equal(lastRequestBody?.model, TEST_MODEL)
|
assert.equal(lastRequestBody?.model, TEST_MODEL)
|
||||||
assert.ok(Array.isArray(lastRequestBody?.messages))
|
assert.ok(Array.isArray(lastRequestBody?.messages))
|
||||||
|
|
||||||
|
console.log("[omp-compat] developer advisory reaches the legacy generate request body")
|
||||||
|
requestCount = 0
|
||||||
|
requestBodies = []
|
||||||
|
providerUpgradeRequired = true
|
||||||
|
const advisoryRun = await runOmp(
|
||||||
|
[
|
||||||
|
"-e",
|
||||||
|
EXT_PATH,
|
||||||
|
"-e",
|
||||||
|
ADVISORY_EXT_PATH,
|
||||||
|
"-p",
|
||||||
|
"say mock token",
|
||||||
|
"--model",
|
||||||
|
`commandcode/${TEST_MODEL}`,
|
||||||
|
"--no-tools",
|
||||||
|
"--no-title",
|
||||||
|
],
|
||||||
|
30_000,
|
||||||
|
)
|
||||||
|
assert.equal(advisoryRun.code, 0, advisoryRun.stderr)
|
||||||
|
assert.match(advisoryRun.stdout, /mock-omp-ok/)
|
||||||
|
|
||||||
|
const promptBodies = requestBodies.filter((body) =>
|
||||||
|
JSON.stringify(body?.params?.messages ?? []).includes("say mock token"),
|
||||||
|
)
|
||||||
|
assert.ok(promptBodies.length >= 1, "expected at least one generate request with the prompt")
|
||||||
|
|
||||||
|
for (const body of promptBodies) {
|
||||||
|
const messages = body?.params?.messages ?? []
|
||||||
|
const advisoryMessages = messages.filter((message) =>
|
||||||
|
JSON.stringify(message).includes("Stop and correct the benchmark."),
|
||||||
|
)
|
||||||
|
assert.equal(advisoryMessages.length, 1, "the advisory should survive conversion exactly once")
|
||||||
|
const advisoryMessage = advisoryMessages[0]
|
||||||
|
assert.equal(advisoryMessage.role, "user")
|
||||||
|
const advisoryText =
|
||||||
|
typeof advisoryMessage.content === "string"
|
||||||
|
? advisoryMessage.content
|
||||||
|
: (advisoryMessage.content ?? [])
|
||||||
|
.map((part) => (part?.type === "text" ? part.text : ""))
|
||||||
|
.join("\n")
|
||||||
|
assert.equal(advisoryText, ADVISORY_XML, "advisory content must arrive verbatim")
|
||||||
|
|
||||||
|
const advisoryIndex = messages.indexOf(advisoryMessage)
|
||||||
|
const promptIndex = messages.findIndex((message) =>
|
||||||
|
JSON.stringify(message).includes("say mock token"),
|
||||||
|
)
|
||||||
|
assert.ok(
|
||||||
|
advisoryIndex < promptIndex,
|
||||||
|
"advisory must keep its chronological position relative to the prompt",
|
||||||
|
)
|
||||||
|
assert.doesNotMatch(
|
||||||
|
String(body?.params?.system ?? ""),
|
||||||
|
/Stop and correct the benchmark|<advisory/,
|
||||||
|
"advisory must not be hoisted into the system prompt",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
console.log("[omp-compat] PASS")
|
console.log("[omp-compat] PASS")
|
||||||
} finally {
|
} finally {
|
||||||
await new Promise((resolve) => server.close(resolve))
|
await new Promise((resolve) => server.close(resolve))
|
||||||
|
|||||||
@@ -741,6 +741,89 @@ describe("messagesToCC()", () => {
|
|||||||
it("handles empty conversations", () => {
|
it("handles empty conversations", () => {
|
||||||
assert.deepEqual(messagesToCC([]), [])
|
assert.deepEqual(messagesToCC([]), [])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("keeps developer messages instead of dropping them", () => {
|
||||||
|
const result = messagesToCC([
|
||||||
|
{ role: "user", content: "start" },
|
||||||
|
{ role: "developer", content: "mid-conversation steering note" },
|
||||||
|
])
|
||||||
|
|
||||||
|
assert.deepEqual(result, [
|
||||||
|
{ role: "user", content: "start" },
|
||||||
|
{ role: "user", content: "mid-conversation steering note" },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("converts developer text parts with the same shape as user content", () => {
|
||||||
|
const result = messagesToCC([
|
||||||
|
{
|
||||||
|
role: "developer",
|
||||||
|
content: [{ type: "text", text: "reminder one" }],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
assert.deepEqual(result, [
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
content: [{ type: "text", text: "reminder one" }],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("preserves advisory XML verbatim in the serialized request messages", () => {
|
||||||
|
const advisory =
|
||||||
|
'<advisory severity="blocker" guidance="weigh, don\'t blindly obey">\nStop and correct the benchmark.\n</advisory>'
|
||||||
|
const serialized = JSON.stringify(messagesToCC([{ role: "developer", content: advisory }]))
|
||||||
|
|
||||||
|
assert.deepEqual(JSON.parse(serialized), [{ role: "user", content: advisory }])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("keeps developer advisories in chronological position without hoisting", () => {
|
||||||
|
const advisory =
|
||||||
|
'<advisory severity="blocker" guidance="weigh, don\'t blindly obey">\nStop and correct the benchmark.\n</advisory>'
|
||||||
|
const result = messagesToCC([
|
||||||
|
{ role: "user", content: "run the benchmark" },
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
content: [
|
||||||
|
{ type: "text", text: "running it" },
|
||||||
|
{ type: "toolCall", id: "c1", name: "bash", arguments: { command: "bench" } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: "toolResult",
|
||||||
|
toolCallId: "c1",
|
||||||
|
toolName: "bash",
|
||||||
|
content: [{ type: "text", text: "benchmark output" }],
|
||||||
|
},
|
||||||
|
{ role: "developer", content: advisory },
|
||||||
|
{ role: "user", content: "continue" },
|
||||||
|
])
|
||||||
|
|
||||||
|
assert.deepEqual(result, [
|
||||||
|
{ role: "user", content: "run the benchmark" },
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
content: [
|
||||||
|
{ type: "text", text: "running it" },
|
||||||
|
{ type: "tool-call", toolCallId: "c1", toolName: "bash", input: { command: "bench" } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: "tool",
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "tool-result",
|
||||||
|
toolCallId: "c1",
|
||||||
|
toolName: "bash",
|
||||||
|
output: { type: "text", value: "benchmark output" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ role: "user", content: advisory },
|
||||||
|
{ role: "user", content: "continue" },
|
||||||
|
])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("parseStreamEventLine()", () => {
|
describe("parseStreamEventLine()", () => {
|
||||||
|
|||||||
@@ -707,6 +707,56 @@ describe("streamCommandCode — request serialization", () => {
|
|||||||
assert.equal(headers["x-session-id"], undefined)
|
assert.equal(headers["x-session-id"], undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("sends developer advisories as user messages in position, without system hoisting", async () => {
|
||||||
|
server.mockResponse({
|
||||||
|
type: "success",
|
||||||
|
events: [JSON.stringify({ type: "finish", finishReason: "stop" })],
|
||||||
|
})
|
||||||
|
const { streamCommandCode } = createTestDeps({ apiBase: server.baseUrl() })
|
||||||
|
const advisory =
|
||||||
|
'<advisory severity="blocker" guidance="weigh, don\'t blindly obey">\nStop and correct the benchmark.\n</advisory>'
|
||||||
|
const context = makeContext({
|
||||||
|
messages: [
|
||||||
|
{ role: "user", content: "run the benchmark" },
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
content: [
|
||||||
|
{ type: "text", text: "running it" },
|
||||||
|
{ type: "toolCall", id: "c1", name: "bash", arguments: { command: "bench" } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: "toolResult",
|
||||||
|
toolCallId: "c1",
|
||||||
|
toolName: "bash",
|
||||||
|
content: [{ type: "text", text: "benchmark output" }],
|
||||||
|
},
|
||||||
|
{ role: "developer", content: advisory },
|
||||||
|
{ role: "user", content: "continue" },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
await collectEvents(streamCommandCode(makeModel(), context, { apiKey: "mock-key" }))
|
||||||
|
|
||||||
|
const body = server.lastRequestBody()
|
||||||
|
assert.deepEqual(
|
||||||
|
objectAt(body, ["params", "messages", "3"]),
|
||||||
|
{ role: "user", content: advisory },
|
||||||
|
"developer advisory should arrive as an in-position user message with identical content",
|
||||||
|
)
|
||||||
|
assert.deepEqual(objectAt(body, ["params", "messages", "4"]), {
|
||||||
|
role: "user",
|
||||||
|
content: "continue",
|
||||||
|
})
|
||||||
|
assert.equal(objectAt(body, ["params", "messages", "5"]), undefined)
|
||||||
|
assert.equal(
|
||||||
|
objectAt(body, ["params", "system"]),
|
||||||
|
"You are a test assistant.",
|
||||||
|
"advisory must not be hoisted into the system prompt",
|
||||||
|
)
|
||||||
|
assert.doesNotMatch(String(objectAt(body, ["params", "system"])), /advisory/)
|
||||||
|
})
|
||||||
|
|
||||||
it("forwards explicit temperature and stable session metadata", async () => {
|
it("forwards explicit temperature and stable session metadata", async () => {
|
||||||
server.mockResponse({
|
server.mockResponse({
|
||||||
type: "success",
|
type: "success",
|
||||||
|
|||||||
Reference in New Issue
Block a user