From 8cf221e13fdb9be43ca533b5f5aa66c87c5a0bd0 Mon Sep 17 00:00:00 2001 From: dualface Date: Sun, 20 Sep 2026 11:57:40 +0800 Subject: [PATCH] fix: read prompt and tools from the normalized transcript pi 0.86.0 hands custom providers a TranscriptContext: `systemPrompt` and `tools` are gone, and tool declarations live in the transcript's system messages. Reading `context.tools` therefore sent zero tools to Devin, so every Devin model answered that it cannot run commands, and the system prompt was dropped with it. Collapse the system messages first, then take the prompt and the current tool set from the replayed transcript. Co-Authored-By: Claude Opus 5 (1M context) --- src/context-map.ts | 23 +++++++++++++++++------ src/stream.ts | 4 ++-- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/context-map.ts b/src/context-map.ts index 3721b4e..3dc107b 100644 --- a/src/context-map.ts +++ b/src/context-map.ts @@ -1,4 +1,11 @@ -import type { Context, Message, Tool } from "@earendil-works/pi-ai"; +import { + collapseSystemMessages, + getCurrentSystemPrompt, + getCurrentTools, + type Message, + type Tool, + type TranscriptContext, +} from "@earendil-works/pi-ai"; export interface ContentPart { type: "text" | "image"; @@ -39,13 +46,17 @@ function userContent(content: Message["content"]): string | ContentPart[] { return parts; } -export function mapContextToChat(context: Context): MappedChat { +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. + const transcript = collapseSystemMessages(context); + const systemPrompt = getCurrentSystemPrompt(transcript.messages); const messages: ChatHistoryItem[] = []; - if (context.systemPrompt) { - messages.push({ role: "system", content: context.systemPrompt }); + if (systemPrompt) { + messages.push({ role: "system", content: systemPrompt }); } - for (const message of context.messages) { + for (const message of transcript.messages) { if (message.role === "user") { messages.push({ role: "user", content: userContent(message.content) }); continue; @@ -86,7 +97,7 @@ export function mapContextToChat(context: Context): MappedChat { } } - const tools: ToolDef[] = (context.tools ?? []).map((tool: Tool) => ({ + const tools: ToolDef[] = getCurrentTools(transcript.messages).map((tool: Tool) => ({ name: tool.name, description: tool.description, parameters: tool.parameters, diff --git a/src/stream.ts b/src/stream.ts index 3cc3eb4..8404605 100644 --- a/src/stream.ts +++ b/src/stream.ts @@ -4,9 +4,9 @@ import { type Api, type AssistantMessage, type AssistantMessageEventStream, - type Context, type Model, type SimpleStreamOptions, + type TranscriptContext, calculateCost, createAssistantMessageEventStream, } from "@earendil-works/pi-ai"; @@ -402,7 +402,7 @@ async function* streamChatEvents(args: { export function streamDevin( model: Model, - context: Context, + context: TranscriptContext, options?: SimpleStreamOptions, ): AssistantMessageEventStream { const stream = createAssistantMessageEventStream();