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) <noreply@anthropic.com>
This commit is contained in:
dualface
2026-09-20 12:08:30 +08:00
co-authored by Claude Opus 5
parent e4d4ec3aae
commit 8cf221e13f
2 changed files with 19 additions and 8 deletions
+17 -6
View File
@@ -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,
+2 -2
View File
@@ -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<Api>,
context: Context,
context: TranscriptContext,
options?: SimpleStreamOptions,
): AssistantMessageEventStream {
const stream = createAssistantMessageEventStream();