WIP: Major cleanup - move Attachment to consumers, simplify agent API

- Removed Attachment from agent package (now in web-ui/coding-agent)
- Agent.prompt now takes (text, images?: ImageContent[])
- Removed transports from web-ui (duplicate of agent package)
- Updated coding-agent to use local message types
- Updated mom package for new agent API

Remaining: Fix AgentInterface.ts to compose UserMessageWithAttachments
This commit is contained in:
Mario Zechner 2025-12-28 10:55:12 +01:00
parent f86dea2e4f
commit 6ddc7418da
57 changed files with 167 additions and 1061 deletions

View file

@ -6,9 +6,9 @@ import type { MessageEditor } from "./MessageEditor.js";
import "./MessageEditor.js";
import "./MessageList.js";
import "./Messages.js"; // Import for side effects to register the custom elements
import type { Agent, AgentEvent } from "../agent/agent.js";
import { getAppStorage } from "../storage/app-storage.js";
import "./StreamingMessageContainer.js";
import type { Agent, AgentEvent } from "@mariozechner/pi-agent-core";
import type { Attachment } from "../utils/attachment-utils.js";
import { formatUsage } from "../utils/format.js";
import { i18n } from "../utils/i18n.js";
@ -130,16 +130,13 @@ export class AgentInterface extends LitElement {
}
if (!this.session) return;
this._unsubscribeSession = this.session.subscribe(async (ev: AgentEvent) => {
if (ev.type === "state-update") {
if (ev.type === "message_update") {
if (this._streamingContainer) {
this._streamingContainer.isStreaming = ev.state.isStreaming;
this._streamingContainer.setMessage(ev.state.streamMessage, !ev.state.isStreaming);
const isStreaming = this.session?.state.isStreaming || false;
this._streamingContainer.isStreaming = isStreaming;
this._streamingContainer.setMessage(ev.message, !isStreaming);
}
this.requestUpdate();
} else if (ev.type === "error-no-model") {
// TODO show some UI feedback
} else if (ev.type === "error-no-api-key") {
// Handled by onApiKeyRequired callback
}
});
}

View file

@ -1,16 +1,15 @@
import type { AgentMessage, AgentTool } from "@mariozechner/pi-agent-core";
import type {
AgentTool,
AssistantMessage as AssistantMessageType,
ToolResultMessage as ToolResultMessageType,
} from "@mariozechner/pi-ai";
import { html, LitElement, type TemplateResult } from "lit";
import { property } from "lit/decorators.js";
import { repeat } from "lit/directives/repeat.js";
import type { AppMessage } from "./Messages.js";
import { renderMessage } from "./message-renderer-registry.js";
export class MessageList extends LitElement {
@property({ type: Array }) messages: AppMessage[] = [];
@property({ type: Array }) messages: AgentMessage[] = [];
@property({ type: Array }) tools: AgentTool[] = [];
@property({ type: Object }) pendingToolCalls?: Set<string>;
@property({ type: Boolean }) isStreaming: boolean = false;

View file

@ -1,6 +1,7 @@
import type {
AgentTool,
AssistantMessage as AssistantMessageType,
ImageContent,
TextContent,
ToolCall,
ToolResultMessage as ToolResultMessageType,
UserMessage as UserMessageType,
@ -12,8 +13,14 @@ import type { Attachment } from "../utils/attachment-utils.js";
import { formatUsage } from "../utils/format.js";
import { i18n } from "../utils/i18n.js";
import "./ThinkingBlock.js";
import type { AgentTool } from "@mariozechner/pi-agent-core";
export type UserMessageWithAttachments = UserMessageType & { attachments?: Attachment[] };
export type UserMessageWithAttachments = {
role: "user-with-attachments";
content: string | (TextContent | ImageContent)[];
timestamp: number;
attachments?: Attachment[];
};
// Artifact message type for session persistence
export interface ArtifactMessage {
@ -25,26 +32,16 @@ export interface ArtifactMessage {
timestamp: string;
}
// Base message union
type BaseMessage = AssistantMessageType | UserMessageWithAttachments | ToolResultMessageType | ArtifactMessage;
// Extensible interface - apps can extend via declaration merging
// Example:
// declare module "@mariozechner/pi-web-ui" {
// interface CustomMessages {
// "system-notification": SystemNotificationMessage;
// }
// }
export interface CustomMessages {
// Empty by default - apps extend via declaration merging
declare module "@mariozechner/pi-agent-core" {
interface CustomAgentMessages {
"user-with-attachment": UserMessageWithAttachments;
artifact: ArtifactMessage;
}
}
// AppMessage is union of base messages + custom messages
export type AppMessage = BaseMessage | CustomMessages[keyof CustomMessages];
@customElement("user-message")
export class UserMessage extends LitElement {
@property({ type: Object }) message!: UserMessageWithAttachments;
@property({ type: Object }) message!: UserMessageWithAttachments | UserMessageType;
protected override createRenderRoot(): HTMLElement | DocumentFragment {
return this;
@ -66,7 +63,9 @@ export class UserMessage extends LitElement {
<div class="user-message-container py-2 px-4 rounded-xl">
<markdown-block .content=${content}></markdown-block>
${
this.message.attachments && this.message.attachments.length > 0
this.message.role === "user-with-attachments" &&
this.message.attachments &&
this.message.attachments.length > 0
? html`
<div class="mt-3 flex flex-wrap gap-2">
${this.message.attachments.map(

View file

@ -1,4 +1,5 @@
import type { AgentTool, Message, ToolResultMessage } from "@mariozechner/pi-ai";
import type { AgentMessage, AgentTool } from "@mariozechner/pi-agent-core";
import type { ToolResultMessage } from "@mariozechner/pi-ai";
import { html, LitElement } from "lit";
import { property, state } from "lit/decorators.js";
@ -9,8 +10,8 @@ export class StreamingMessageContainer extends LitElement {
@property({ type: Object }) toolResultsById?: Map<string, ToolResultMessage>;
@property({ attribute: false }) onCostClick?: () => void;
@state() private _message: Message | null = null;
private _pendingMessage: Message | null = null;
@state() private _message: AgentMessage | null = null;
private _pendingMessage: AgentMessage | null = null;
private _updateScheduled = false;
private _immediateUpdate = false;
@ -24,7 +25,7 @@ export class StreamingMessageContainer extends LitElement {
}
// Public method to update the message with batching for performance
public setMessage(message: Message | null, immediate = false) {
public setMessage(message: AgentMessage | null, immediate = false) {
// Store the latest message
this._pendingMessage = message;

View file

@ -1,11 +1,11 @@
import type { AgentMessage } from "@mariozechner/pi-agent-core";
import type { TemplateResult } from "lit";
import type { AppMessage } from "./Messages.js";
// Extract role type from AppMessage union
export type MessageRole = AppMessage["role"];
export type MessageRole = AgentMessage["role"];
// Generic message renderer typed to specific message type
export interface MessageRenderer<TMessage extends AppMessage = AppMessage> {
export interface MessageRenderer<TMessage extends AgentMessage = AgentMessage> {
render(message: TMessage): TemplateResult;
}
@ -14,7 +14,7 @@ const messageRenderers = new Map<MessageRole, MessageRenderer<any>>();
export function registerMessageRenderer<TRole extends MessageRole>(
role: TRole,
renderer: MessageRenderer<Extract<AppMessage, { role: TRole }>>,
renderer: MessageRenderer<Extract<AgentMessage, { role: TRole }>>,
): void {
messageRenderers.set(role, renderer);
}
@ -23,6 +23,6 @@ export function getMessageRenderer(role: MessageRole): MessageRenderer | undefin
return messageRenderers.get(role);
}
export function renderMessage(message: AppMessage): TemplateResult | undefined {
export function renderMessage(message: AgentMessage): TemplateResult | undefined {
return messageRenderers.get(message.role)?.render(message);
}