mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix(aibridge/intercept/messages): record user prompt before trailing system message (#26195)
_Disclosure: produced using Opus 4.8_ I've noticed recently that some prompts are not displaying correctly. <img width="1388" height="509" alt="image" src="https://github.com/user-attachments/assets/fad3812f-e42d-4398-a16c-a0e7f924455d" /> The cause is the `mid-conversation-system-2026-04-07` beta. When enabled, the client appends a trailing `role: "system"` message after the user's text (e.g. an injected skills list): ```json "messages": [ { "role": "user", "content": [ "hey how are ya" ] }, { "role": "system", "content": "The following skills are available..." } ] ``` Our prompt detection algo was being subverted since we only check the last message if role=user.
This commit is contained in:
@@ -57,6 +57,7 @@ var (
|
||||
constTool = string(constant.ValueOf[constant.Tool]())
|
||||
constToolResult = string(constant.ValueOf[constant.ToolResult]())
|
||||
constUser = string(anthropic.MessageParamRoleUser)
|
||||
constSystem = "system"
|
||||
|
||||
// bedrockUnsupportedFields are top-level fields present in the Anthropic Messages
|
||||
// API that are absent from the Bedrock request body schema. Sending them results
|
||||
@@ -166,6 +167,16 @@ func (p RequestPayload) lastUserPrompt() (string, bool, error) {
|
||||
}
|
||||
|
||||
lastMessage := messageItems[len(messageItems)-1]
|
||||
// Clients using the mid-conversation system beta (e.g. Claude Code with
|
||||
// anthropic-beta: mid-conversation-system-*) append a trailing role=system
|
||||
// message after the user's prompt, such as an injected skills list. When the
|
||||
// last message is that system message, step back exactly one message to find
|
||||
// the user's prompt. We only step back past a single trailing system message
|
||||
// so we never re-record a stale prompt from an earlier turn that contained no
|
||||
// new user input. See https://docs.claude.com/en/api/beta-headers.
|
||||
if lastMessage.Get(messagesReqFieldRole).String() == constSystem && len(messageItems) >= 2 {
|
||||
lastMessage = messageItems[len(messageItems)-2]
|
||||
}
|
||||
if lastMessage.Get(messagesReqFieldRole).String() != constUser {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
@@ -202,6 +202,41 @@ func TestRequestPayloadLastUserPrompt(t *testing.T) {
|
||||
expectedFound: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "trailing system message steps back to user prompt",
|
||||
requestBody: `{"model":"claude-opus-4-5","max_tokens":1024,"messages":[{"role":"user","content":"hello"},{"role":"system","content":"available skills: ..."}]}`,
|
||||
expectedPrompt: "hello",
|
||||
expectedFound: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "trailing system message with typed user content returns last text block",
|
||||
requestBody: `{"model":"claude-opus-4-5","max_tokens":1024,"messages":[{"role":"user","content":[{"type":"text","text":"first"},{"type":"text","text":"last"}]},{"role":"system","content":"available skills: ..."}]}`,
|
||||
expectedPrompt: "last",
|
||||
expectedFound: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "trailing system message after non user does not record",
|
||||
requestBody: `{"model":"claude-opus-4-5","max_tokens":1024,"messages":[{"role":"assistant","content":"response"},{"role":"system","content":"available skills: ..."}]}`,
|
||||
expectedPrompt: "",
|
||||
expectedFound: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "only system message does not step out of bounds",
|
||||
requestBody: `{"model":"claude-opus-4-5","max_tokens":1024,"messages":[{"role":"system","content":"available skills: ..."}]}`,
|
||||
expectedPrompt: "",
|
||||
expectedFound: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "two trailing system messages only steps back once",
|
||||
requestBody: `{"model":"claude-opus-4-5","max_tokens":1024,"messages":[{"role":"user","content":"hello"},{"role":"system","content":"a"},{"role":"system","content":"b"}]}`,
|
||||
expectedPrompt: "",
|
||||
expectedFound: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "messages wrong type returns error",
|
||||
requestBody: `{"model":"claude-opus-4-5","max_tokens":1024,"messages":{}}`,
|
||||
|
||||
Reference in New Issue
Block a user