fix: send system instructions in Devin's dedicated prompt field

This commit is contained in:
Kashyab Ambarani
2026-09-22 15:38:37 -07:00
parent f42b776b8e
commit 3b40b4020d
5 changed files with 188 additions and 51 deletions
+7 -9
View File
@@ -2,6 +2,7 @@ import {
collapseSystemMessages,
getCurrentSystemPrompt,
getCurrentTools,
withoutInitialSystemMessage,
type Message,
type Tool,
type TranscriptContext,
@@ -15,7 +16,7 @@ export interface ContentPart {
}
export interface ChatHistoryItem {
role: "user" | "assistant" | "system" | "tool";
role: "user" | "assistant" | "tool";
content: string | ContentPart[];
tool_call_id?: string;
tool_calls?: Array<{ id: string; name: string; arguments: string }>;
@@ -28,6 +29,8 @@ export interface ToolDef {
}
export interface MappedChat {
/** Sent in GetChatMessageRequest.prompt, separately from conversation history. */
systemPrompt?: string;
messages: ChatHistoryItem[];
tools: ToolDef[];
}
@@ -47,16 +50,11 @@ function userContent(content: Message["content"]): string | ContentPart[] {
}
export function mapContextToChat(context: TranscriptContext): MappedChat {
// Devin takes one leading system message and knows no mid-conversation system
// message, so replay the transcript deltas into the leading one first.
// Fold later system updates into the single prompt sent separately to Devin.
const transcript = collapseSystemMessages(context);
const systemPrompt = getCurrentSystemPrompt(transcript.messages);
const messages: ChatHistoryItem[] = [];
if (systemPrompt) {
messages.push({ role: "system", content: systemPrompt });
}
for (const message of transcript.messages) {
for (const message of withoutInitialSystemMessage(transcript.messages)) {
if (message.role === "user") {
messages.push({ role: "user", content: userContent(message.content) });
continue;
@@ -103,5 +101,5 @@ export function mapContextToChat(context: TranscriptContext): MappedChat {
parameters: tool.parameters,
}));
return { messages, tools };
return { systemPrompt: systemPrompt || undefined, messages, tools };
}
+8 -38
View File
@@ -23,10 +23,9 @@ import {
iterFields,
} from "./wire.js";
const SOURCE_BY_ROLE: Record<string, number> = {
const SOURCE_BY_ROLE: Record<ChatHistoryItem["role"], number> = {
user: 1,
assistant: 2,
system: 1,
tool: 4,
};
@@ -50,40 +49,6 @@ function normalizeContent(content: string | ContentPart[]): ContentPart[] {
return content;
}
function collapseSystemIntoUser(messages: ChatHistoryItem[]): ChatHistoryItem[] {
const out: ChatHistoryItem[] = [];
let pending: string[] = [];
const textOf = (content: string | ContentPart[]) =>
normalizeContent(content)
.filter((part) => part.type === "text" && part.text)
.map((part) => part.text as string)
.join("\n");
for (const message of messages) {
if (message.role === "system") {
const text = textOf(message.content);
if (text) pending.push(text);
continue;
}
if (message.role === "user" && pending.length > 0) {
const userParts = normalizeContent(message.content);
const userText = textOf(userParts);
const images = userParts.filter((part) => part.type === "image");
out.push({
role: "user",
content: [{ type: "text", text: `<system>\n${pending.join("\n\n")}\n</system>\n${userText}` }, ...images],
});
pending = [];
continue;
}
out.push(message);
}
if (pending.length > 0) {
out.push({ role: "user", content: `<system>\n${pending.join("\n\n")}\n</system>` });
}
return out;
}
function encodeImageData(img: { mimeType?: string; base64Data?: string }): Buffer {
return Buffer.concat([
encodeString(1, img.base64Data ?? ""),
@@ -144,6 +109,7 @@ function buildGetChatMessageRequest(args: {
apiKey: string;
userJwt: string;
modelUid: string;
systemPrompt?: string;
messages: ChatHistoryItem[];
tools?: ToolDef[];
cascadeId: string;
@@ -160,10 +126,10 @@ function buildGetChatMessageRequest(args: {
requestId: args.requestId,
triggerId: args.triggerId,
});
const prompts = collapseSystemIntoUser(args.messages).map((message) =>
const prompts = args.messages.map((message) =>
encodeMessage(
3,
encodeChatMessagePrompt(normalizeContent(message.content), SOURCE_BY_ROLE[message.role] ?? 1, {
encodeChatMessagePrompt(normalizeContent(message.content), SOURCE_BY_ROLE[message.role], {
toolCallId: message.role === "tool" ? message.tool_call_id : undefined,
toolCalls: message.role === "assistant" ? message.tool_calls : undefined,
}),
@@ -171,6 +137,7 @@ function buildGetChatMessageRequest(args: {
);
return Buffer.concat([
encodeMessage(1, metadata),
...(args.systemPrompt ? [encodeString(2, args.systemPrompt)] : []),
...prompts,
encodeVarintField(7, 5),
encodeMessage(8, encodeCompletionConfiguration(args.maxOutputTokens)),
@@ -271,6 +238,7 @@ async function* streamChatEvents(args: {
apiKey: string;
host: string;
modelUid: string;
systemPrompt?: string;
messages: ChatHistoryItem[];
tools?: ToolDef[];
maxOutputTokens?: number;
@@ -283,6 +251,7 @@ async function* streamChatEvents(args: {
apiKey: args.apiKey,
userJwt,
modelUid: args.modelUid,
systemPrompt: args.systemPrompt,
messages: args.messages,
tools: args.tools,
cascadeId: ids.cascadeId,
@@ -482,6 +451,7 @@ export function streamDevin(
apiKey,
host,
modelUid,
systemPrompt: mapped.systemPrompt,
messages: mapped.messages,
tools: mapped.tools.length > 0 ? mapped.tools : undefined,
maxOutputTokens: options?.maxTokens,