pi-remote-control/extensions/remote-control/messages.ts

134 lines
3.1 KiB
TypeScript

/**
* Wire protocol types and message serialization for remote-control.
*
* Converts pi session entries into the simplified RenderMsg format
* consumed by the browser client.
*/
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
interface RawContent {
type: string;
text?: string;
id?: string;
name?: string;
arguments?: unknown;
}
export interface RawMessage {
role: string;
content: string | RawContent[];
model?: string;
toolName?: string;
toolCallId?: string;
isError?: boolean;
}
export interface RenderMsg {
id: string; // SessionEntry id, or "pending" while streaming
role: "user" | "assistant" | "tool_result";
text: string;
toolCalls?: Array<{ id: string; name: string; args: string }>;
toolName?: string;
toolCallId?: string;
isError?: boolean;
model?: string;
}
export function serializeMessage(
id: string,
msg: RawMessage,
): RenderMsg | null {
if (msg.role === "user") {
const text =
typeof msg.content === "string"
? msg.content
: (msg.content as RawContent[])
.filter((c) => c.type === "text")
.map((c) => c.text)
.join("");
return { id, role: "user", text };
}
if (msg.role === "assistant") {
const text = (msg.content as RawContent[])
.filter((c) => c.type === "text")
.map((c) => c.text)
.join("");
const toolCalls = (msg.content as RawContent[])
.filter((c) => c.type === "toolCall")
.map((c) => ({
id: c.id,
name: c.name,
args: JSON.stringify(c.arguments, null, 2),
}));
return {
id,
role: "assistant",
text,
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
model: msg.model,
};
}
if (msg.role === "toolResult") {
const text = (msg.content as RawContent[])
.filter((c) => c.type === "text")
.map((c) => c.text)
.join("");
return {
id,
role: "tool_result",
text,
toolName: msg.toolName,
toolCallId: msg.toolCallId,
isError: msg.isError,
};
}
return null;
}
export function getBranchMessages(ctx: ExtensionContext): RenderMsg[] {
const branch = ctx.sessionManager.getBranch();
const out: RenderMsg[] = [];
for (const entry of branch) {
if (entry.type !== "message") continue;
const m = serializeMessage(
entry.id,
(entry as { id: string; type: string; message: RawMessage }).message,
);
if (m) out.push(m);
}
return out;
}
function abbreviateHome(p: string): string {
const home = process.env.HOME;
if (home && p === home) return "~";
if (home && p.startsWith(`${home}/`)) return `~${p.slice(home.length)}`;
return p;
}
export function buildSyncMessage(ctx: ExtensionContext): {
type: "sync";
messages: RenderMsg[];
state: {
isStreaming: boolean;
model: string | undefined;
cwd: string;
sessionName: string | undefined;
};
} {
return {
type: "sync",
messages: getBranchMessages(ctx),
state: {
isStreaming: !ctx.isIdle(),
model: ctx.model?.id,
cwd: abbreviateHome(ctx.cwd),
sessionName: ctx.sessionManager.getSessionName(),
},
};
}