This repository has been archived on 2026-08-23. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
pi-web/src/shared/apiTypes.ts
T
Federico Jaramillo Martinez d17050e144 feat: add image attachments to the chat composer
Support pasting (Ctrl/Cmd+V), drag-and-drop, and an Attach button to add
PNG/JPEG/GIF/WebP images to a message, with thumbnail previews and
multi-image support.

Attachments are delivered to the session using pi's native ImageContent
format and are run through pi's own resizeImage so they match pi's inline
image limits exactly. Image content now renders inline in the transcript.

A per-message delivery toggle also lets users save attachments into the
workspace `.pi-web/paste` folder and reference them so the agent reads
them with its own tools.

The accepted HTTP upload size is configurable via PI_WEB_MAX_UPLOAD_BYTES
or the maxUploadBytes config value (default 64 MB).

Closes #13
2026-06-13 13:49:39 +02:00

505 lines
13 KiB
TypeScript

export type MachineKind = "local" | "remote";
export type MachineStatus = "unknown" | "online" | "offline" | "error";
export const PI_WEB_CAPABILITIES = {
sessionsDeleteArchived: "sessions.deleteArchived",
promptAttachments: "prompt.attachments",
} as const;
export type PiWebCapability = typeof PI_WEB_CAPABILITIES[keyof typeof PI_WEB_CAPABILITIES];
export interface Machine {
id: string;
name: string;
kind: MachineKind;
baseUrl?: string;
createdAt: string;
updatedAt: string;
status?: MachineStatus;
statusMessage?: string;
}
export interface MachineHealth {
machineId: string;
ok: boolean;
checkedAt: string;
status?: MachineStatus;
web?: PiWebComponentStatus;
sessiond?: PiWebComponentStatus;
error?: string;
}
export interface MachineRuntime {
machineId: string;
ok: boolean;
checkedAt: string;
packageName?: string;
generatedAt?: string;
components?: PiWebRuntimeResponse["components"];
capabilities?: PiWebCapability[];
error?: string;
}
export type PiWebShortcutConfig = Record<string, string | null>;
export type PiWebPluginSettings = Record<string, unknown>;
export type PiWebPluginConfigMap = Record<string, PiWebPluginConfig>;
export interface PiWebPluginConfig {
enabled?: boolean;
settings?: PiWebPluginSettings;
[key: string]: unknown;
}
export interface PiWebConfigValues {
host?: string;
port?: number;
allowedHosts?: string[] | true;
shortcuts?: PiWebShortcutConfig;
plugins?: PiWebPluginConfigMap;
/** Maximum accepted HTTP request body size in bytes (uploads/attachments). */
maxUploadBytes?: number;
}
export type PiWebPluginScope = "bundled" | "local" | "user" | "project";
export interface PiWebPluginInfo {
id: string;
module: string;
source: string;
scope: PiWebPluginScope;
machineSpecific: boolean;
enabled: boolean;
}
export interface PiWebPluginsResponse {
plugins: PiWebPluginInfo[];
}
export interface PiWebConfigEnvOverrides {
host: boolean;
port: boolean;
allowedHosts: boolean;
}
export interface PiWebConfigResponse {
path: string;
exists: boolean;
config: PiWebConfigValues;
effectiveConfig: PiWebConfigValues;
envOverrides: PiWebConfigEnvOverrides;
}
export interface Project {
id: string;
name: string;
path: string;
createdAt: string;
}
export interface Workspace {
id: string;
projectId: string;
path: string;
label: string;
branch?: string;
isMain: boolean;
isGitRepo: boolean;
isGitWorktree: boolean;
}
export interface SessionRef {
id: string;
cwd: string;
}
export interface SessionInfo extends SessionRef {
path: string;
name?: string;
created: string;
modified: string;
messageCount: number;
firstMessage: string;
parentSessionPath?: string;
archived?: boolean;
archivedAt?: string;
}
export interface ArchiveSessionsResponse {
archived: true;
sessionIds?: string[];
archivedCount?: number;
skippedAlreadyArchivedCount?: number;
}
export interface SessionActivity {
sessionId: string;
phase: "active" | "idle" | "error";
label: string;
detail?: string;
at: string;
}
export interface QueuedSessionMessage {
kind: "steer" | "followUp";
text: string;
}
/**
* A binary attachment carried with a prompt. The wire format mirrors pi's own
* `ImageContent` shape (`{ type: "image", data, mimeType }`) so attachments are
* fully compatible with the underlying pi coding agent.
*/
export interface PromptAttachment {
/** Kind of attachment. Only images are supported by pi today. */
kind: "image";
/** IANA mime type (for example "image/png"). */
mimeType: string;
/** Base64-encoded binary payload (no data: URL prefix). */
data: string;
/** Optional original filename, used for previews and folder-mode filenames. */
name?: string;
}
/**
* How prompt attachments should be delivered to the session.
* - "inline": send the binary to pi as native image content (multimodal input).
* - "folder": save the file into the workspace and reference it from the prompt
* text so the agent reads it with its own tools.
*/
export type PromptAttachmentDelivery = "inline" | "folder";
export interface SavedPromptAttachment {
/** Workspace-relative path the attachment was written to. */
path: string;
mimeType: string;
size: number;
}
export interface SaveAttachmentsResponse {
attachments: SavedPromptAttachment[];
}
export interface SessionModel {
provider?: string;
id?: string;
name?: string;
contextWindow?: number;
reasoning?: unknown;
}
export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
export type AuthType = "oauth" | "api_key";
export type AuthStatusSource = "stored" | "runtime" | "environment" | "fallback" | "models_json_key" | "models_json_command";
export interface AuthProviderStatus {
configured: boolean;
source?: AuthStatusSource;
label?: string;
}
export interface AuthProviderOption {
id: string;
name: string;
authType: AuthType;
status: AuthProviderStatus;
}
export interface AuthProvidersResponse {
providers: AuthProviderOption[];
}
export interface OAuthFlowState {
flowId: string;
providerId: string;
providerName: string;
status: "running" | "complete" | "error" | "cancelled";
auth?: { url: string; instructions?: string };
prompt?: { requestId: string; message: string; placeholder?: string; allowEmpty?: boolean; kind: "prompt" | "manual" };
select?: { requestId: string; message: string; options: CommandOption[] };
progress: string[];
error?: string;
}
export interface ModelSelectionResponse {
models: SessionModel[];
}
export interface ThinkingLevelsResponse {
levels: ThinkingLevel[];
}
export interface SessionStatus {
sessionId: string;
model?: SessionModel;
thinkingLevel?: string;
isStreaming: boolean;
isCompacting: boolean;
isBashRunning: boolean;
pendingMessageCount: number;
queuedMessages: QueuedSessionMessage[];
messageCount?: number;
tokens: { input: number; output: number; cacheRead: number; cacheWrite: number; total: number };
cost: number;
contextUsage?: { tokens: number | null; contextWindow: number; percent: number | null };
}
export interface WorkspaceActivity {
cwd: string;
hasSessionActivity: boolean;
hasTerminalActivity: boolean;
updatedAt: string;
}
export interface WorkspaceActivityResponse {
workspaces: WorkspaceActivity[];
generatedAt: string;
}
export interface SlashCommand {
name: string;
description?: string;
source: "extension" | "prompt" | "skill" | "builtin";
}
export interface FileSuggestion {
path: string;
kind: "tracked" | "untracked" | "other";
}
export interface FileTreeEntry {
name: string;
path: string;
type: "file" | "directory" | "symlink";
size?: number;
modifiedAt?: string;
}
export interface FileTreeResponse {
path: string;
entries: FileTreeEntry[];
scannedAt: string;
truncated: boolean;
}
export type FileContentMediaType = "image";
export interface FileContentResponse {
path: string;
language?: string;
mediaType?: FileContentMediaType;
mimeType?: string;
encoding: "utf8";
size: number;
modifiedAt: string;
content: string;
truncated: boolean;
binary: boolean;
}
export type GitFileState = "unmodified" | "modified" | "added" | "deleted" | "renamed" | "copied" | "untracked" | "ignored" | "conflicted";
export interface GitStatusFile {
path: string;
oldPath?: string;
index: GitFileState;
workingTree: GitFileState;
}
export interface GitStatusResponse {
isGitRepo: boolean;
hash: string;
branch?: string;
upstream?: string;
ahead?: number;
behind?: number;
files: GitStatusFile[];
}
export interface GitDiffResponse {
path?: string;
staged: boolean;
hash: string;
diff: string;
truncated: boolean;
}
export interface TerminalInfo {
id: string;
cwd: string;
name: string;
createdAt: string;
exited: boolean;
exitCode?: number;
commandRunId?: string;
}
export type TerminalCommandRunStatus = "queued" | "running" | "succeeded" | "failed";
export interface TerminalCommandRun {
id: string;
origin: string;
projectId: string;
workspaceId: string;
terminalId: string;
title: string;
command: string;
status: TerminalCommandRunStatus;
exitCode?: number;
createdAt: string;
startedAt?: string;
completedAt?: string;
metadata: Record<string, string>;
}
export interface RunTerminalCommandInput {
workspace: Workspace;
title: string;
command: string;
metadata?: Record<string, string>;
open?: boolean;
}
export interface TerminalCommandRunHandle {
run: TerminalCommandRun;
completed: Promise<TerminalCommandRun>;
}
export interface TerminalCommandRunFilter {
projectId?: string;
workspaceId?: string;
terminalId?: string;
statuses?: TerminalCommandRunStatus[];
metadata?: Record<string, string>;
}
export type PiWebServiceComponent = "web" | "sessiond";
export type PiWebStatusSeverity = "info" | "warning" | "error";
export type PiWebInstallationKind = "pi-package" | "npm-global" | "local" | "unknown";
export interface PiWebInstallationInfo {
kind: PiWebInstallationKind;
path?: string;
source?: string;
scope?: "user" | "project";
npmRoot?: string;
}
export interface PiWebComponentStatus {
component: PiWebServiceComponent;
label: string;
runtimeVersion?: string;
installedVersion?: string;
stale: boolean;
available: boolean;
installation?: PiWebInstallationInfo;
error?: string;
}
export interface PiWebRuntimeComponent {
component: PiWebServiceComponent;
label: string;
runtimeVersion?: string;
available: boolean;
capabilities: PiWebCapability[];
error?: string;
}
export interface PiWebReleaseStatus {
packageName: string;
latestVersion?: string;
updateAvailable: boolean;
checkedAt?: string;
skipped?: boolean;
error?: string;
}
export interface PiWebStatusMessage {
id: string;
severity: PiWebStatusSeverity;
title: string;
body: string;
command?: string;
}
export interface PiWebVersionResponse {
packageName: string;
generatedAt: string;
components: {
web: PiWebComponentStatus;
sessiond: PiWebComponentStatus;
};
}
export interface PiWebRuntimeResponse {
packageName: string;
generatedAt: string;
components: {
web: PiWebRuntimeComponent;
sessiond: PiWebRuntimeComponent;
};
capabilities: PiWebCapability[];
}
export interface PiWebStatusResponse extends PiWebVersionResponse {
release: PiWebReleaseStatus;
commands: {
update?: string;
restart?: string;
restartWeb?: string;
restartSessiond?: string;
status?: string;
};
messages: PiWebStatusMessage[];
}
export type TerminalUiEvent =
| { type: "terminal.created"; terminal: TerminalInfo }
| { type: "terminal.exited"; terminal: TerminalInfo }
| { type: "terminal.closed"; terminalId: string; cwd: string };
export interface WorkspaceActivityUiEvent {
type: "workspace.activity";
activity: WorkspaceActivity;
}
export interface CommandOption {
value: string;
label: string;
description?: string;
}
export interface MessagePage {
messages: unknown[];
start: number;
total: number;
}
export type CommandResult =
| { type: "done"; message?: string; session?: SessionInfo; promptDraft?: string }
| { type: "select"; requestId: string; title: string; options: CommandOption[] }
| { type: "unsupported"; message: string };
export type SessionUiEvent =
| { type: "message.append"; message: unknown }
| { type: "assistant.delta"; text: string }
| { type: "assistant.thinking.delta"; text: string }
| { type: "tool.start"; toolName: string; toolCallId: string; summary: string; args?: unknown }
| { type: "tool.update"; toolName: string; toolCallId: string; text: string; content?: unknown; details?: unknown }
| { type: "tool.end"; toolName: string; toolCallId: string; text: string; isError: boolean; content?: unknown; details?: unknown }
| { type: "shell.start"; command: string; excludeFromContext?: boolean }
| { type: "shell.chunk"; chunk: string }
| { type: "shell.end"; output?: string; exitCode?: number | null; cancelled?: boolean; truncated?: boolean; fullOutputPath?: string; isError?: boolean }
| { type: "agent.start" }
| { type: "agent.end" }
| { type: "message.end"; message?: unknown }
| { type: "status.update"; status: SessionStatus }
| { type: "activity.update"; activity: SessionActivity }
| { type: "command.output"; level: "info" | "success" | "error"; message: string }
| { type: "session.error"; message: string }
| { type: "session.name"; sessionId: string; name?: string }
| { type: "pi.event"; eventType: string };
export type GlobalSessionEvent = Extract<SessionUiEvent, { type: "status.update" | "activity.update" | "session.name" }>;
export type RealtimeEvent = GlobalSessionEvent | TerminalUiEvent | WorkspaceActivityUiEvent;