open-design/packages/contracts/src/sse/chat.ts
2026-05-31 08:07:19 +02:00

97 lines
3.4 KiB
TypeScript

import type { LiveArtifactRefreshStatus } from '../api/live-artifacts.js';
import type { LinkedRepoChangeSummary } from '../api/chat.js';
import type { SseErrorPayload } from '../errors.js';
import type { SseTransportEvent } from './common.js';
export type LiveArtifactSseAction = 'created' | 'updated' | 'deleted';
export type LiveArtifactRefreshSsePhase = 'started' | 'succeeded' | 'failed';
export interface LiveArtifactSsePayload {
type: 'live_artifact';
action: LiveArtifactSseAction;
projectId: string;
artifactId: string;
title: string;
/**
* Refresh lifecycle state of the artifact at emit time. Typed against the
* canonical `LiveArtifactRefreshStatus` enum used by the REST API so that
* SSE consumers (web, CLI) can switch on the same union members without
* widening to `string`. Optional because the daemon may omit the field on
* legacy events; consumers must still null-check before narrowing.
*/
refreshStatus?: LiveArtifactRefreshStatus;
}
export interface LiveArtifactRefreshSsePayload {
type: 'live_artifact_refresh';
phase: LiveArtifactRefreshSsePhase;
projectId: string;
artifactId: string;
refreshId?: string;
title?: string;
refreshedSourceCount?: number;
error?: string;
}
/**
* Emitted by the daemon on `/api/projects/:id/events` when a new
* conversation is inserted into a project from a path the open
* project view can't observe through its own state — currently
* Routines "Run now" in reuse-an-existing-project mode (#1361).
*
* Lives in `packages/contracts` so the daemon producer and the web
* consumer share one type and can't drift as the stream grows.
*/
export interface ProjectConversationCreatedSsePayload {
type: 'conversation-created';
projectId: string;
conversationId: string;
title: string | null;
createdAt: number;
}
export const CHAT_SSE_PROTOCOL_VERSION = 1;
export interface ChatSseStartPayload {
runId?: string;
agentId?: string;
bin: string;
protocolVersion?: typeof CHAT_SSE_PROTOCOL_VERSION;
/** Legacy daemon-internal absolute cwd. Kept for compatibility during W2 adoption. */
cwd?: string | null;
projectId?: string | null;
model?: string | null;
reasoning?: string | null;
}
export interface ChatSseChunkPayload {
chunk: string;
}
export interface ChatSseEndPayload {
code: number | null;
signal?: string | null;
status?: 'succeeded' | 'failed' | 'canceled';
}
export type DaemonAgentPayload =
| { type: 'status'; label: string; model?: string; ttftMs?: number; detail?: string }
| { type: 'text_delta'; delta: string }
| { type: 'thinking_delta'; delta: string }
| { type: 'thinking_start' }
| LiveArtifactSsePayload
| LiveArtifactRefreshSsePayload
| { type: 'tool_use'; id: string; name: string; input: unknown }
| { type: 'tool_result'; toolUseId: string; content: string; isError?: boolean }
| { type: 'usage'; usage?: { input_tokens?: number; output_tokens?: number }; costUsd?: number; durationMs?: number }
| { type: 'fabricated_role_marker'; marker: string; messageId?: string }
| { type: 'raw'; line: string };
export type ChatSseEvent =
| SseTransportEvent<'start', ChatSseStartPayload>
| SseTransportEvent<'agent', DaemonAgentPayload>
| SseTransportEvent<'repo_changes', LinkedRepoChangeSummary>
| SseTransportEvent<'stdout', ChatSseChunkPayload>
| SseTransportEvent<'stderr', ChatSseChunkPayload>
| SseTransportEvent<'error', SseErrorPayload>
| SseTransportEvent<'end', ChatSseEndPayload>;