/** * Vendored types for the Factory Droid backend. * * Mirrors the Command Code backend's parallel type set (ModelLike, ContextLike, * AssistantMessageEvent, CoreDependencies…) so the stream engine is fully * testable with DI and structurally compatible with pi-ai at the boundary. */ export type StopReason = "stop" | "length" | "toolUse" export type ErrorReason = "error" | "aborted" export type TerminalReason = StopReason | ErrorReason export interface UsageCost { input: number output: number cacheRead: number cacheWrite: number total: number } export interface Usage { input: number output: number cacheRead: number cacheWrite: number totalTokens: number cost: UsageCost } export interface TextContent { type: "text" text: string } export interface ThinkingContent { type: "thinking" thinking: string } export interface ToolCallContent { type: "toolCall" id: string name: string arguments: Record } export type AssistantContent = TextContent | ThinkingContent | ToolCallContent export interface AssistantMessageLike { role: "assistant" content: AssistantContent[] api: unknown provider: string model: string usage: Usage stopReason: TerminalReason errorMessage?: string timestamp: number } export interface ModelCost { input: number output: number cacheRead: number cacheWrite: number } export interface ModelLike { id: string api: unknown provider: string maxTokens: number cost: ModelCost } export interface MessageLike { role: string content?: unknown toolCallId?: string toolName?: string isError?: boolean } export interface ContextLike { systemPrompt?: string messages?: readonly MessageLike[] tools?: readonly ToolLike[] } export interface ToolLike { name: string description?: string parameters?: unknown } export interface StreamOptions { apiKey?: string signal?: AbortSignal headers?: Record maxTokens?: number onPayload?: (payload: unknown, model: ModelLike) => unknown | Promise onResponse?: ( response: { status: number; headers: Record }, model: ModelLike, ) => void | Promise /** pi's stable per-conversation id — used as the Factory session key. */ sessionId?: string /** pi's reasoning-effort option; mapped to sessionSettings.reasoningEffort. */ reasoningEffort?: string timeoutMs?: number maxRetries?: number maxRetryDelayMs?: number } export type AssistantMessageEvent = | { type: "start"; partial: AssistantMessageLike } | { type: "text_start"; contentIndex: number; partial: AssistantMessageLike } | { type: "text_delta" contentIndex: number delta: string partial: AssistantMessageLike } | { type: "text_end" contentIndex: number content: string partial: AssistantMessageLike } | { type: "thinking_start"; contentIndex: number; partial: AssistantMessageLike } | { type: "thinking_delta" contentIndex: number delta: string partial: AssistantMessageLike } | { type: "thinking_end" contentIndex: number content: string partial: AssistantMessageLike } | { type: "toolcall_start"; contentIndex: number; partial: AssistantMessageLike } | { type: "toolcall_end" contentIndex: number toolCall: ToolCallContent partial: AssistantMessageLike } | { type: "done"; reason: StopReason; message: AssistantMessageLike } | { type: "error"; reason: ErrorReason; error: AssistantMessageLike } export interface AssistantMessageEventStreamLike extends AsyncIterable { push(event: AssistantMessageEvent): void end(): void } // --------------------------------------------------------------------------- // Factory REST response shapes (minimal subset used by the stream engine) // --------------------------------------------------------------------------- export interface FactoryTokenUsage { inputTokens?: number outputTokens?: number cacheCreationTokens?: number cacheReadTokens?: number thinkingTokens?: number factoryCredits?: number } export interface FactorySession { sessionId: string status: "idle" | "pending" | "running" tokenUsage?: FactoryTokenUsage } export type FactoryContentBlock = | { type: "text"; text: string } | { type: "thinking"; thinking: string } | { type: "tool_use"; name: string; input: unknown } | { type: "tool_result"; content: unknown } | { type: "image"; source: unknown } | { type: "redacted_thinking"; data: string } export interface FactoryMessage { id: string role: "user" | "assistant" | "tool" | "system" content?: FactoryContentBlock[] createdAt: number parentId?: string isError?: boolean } export interface FactoryComputer { id: string name: string providerType: "byom" | "e2b" status?: "provisioning" | "active" | "error" } export interface FactoryMachineTemplate { templateId: string templateName: string repoUrl: string defaultBranch: string buildStatus?: { status: "building" | "success" | "failed" failureReason?: "setup_script_error" | "system_error" } } // --------------------------------------------------------------------------- // DI dependencies (mirrors Command Code CoreDependencies) // --------------------------------------------------------------------------- export interface FactoryDroidDependencies { createStream: () => AssistantMessageEventStreamLike calculateCost: (model: ModelLike, usage: Usage) => void apiBase?: string fetchImpl?: typeof fetch authPaths?: readonly string[] env?: NodeJS.ProcessEnv cwd?: () => string now?: () => number uuid?: () => string homeDir?: () => string delay?: (ms: number, signal: AbortSignal) => Promise /** In-memory per-conversation session state (injectable for tests). */ state?: FactoryDroidState } export interface FactoryDroidState { /** pi conversation id → Factory session id. */ sessionByConversation: Map /** Factory session id → last-seen cumulative token usage (for per-turn deltas). */ lastUsageBySession: Map }