diff --git a/.ai-work/02-extract-shared-dto-event-types.md b/.ai-work/02-extract-shared-dto-event-types.md index 09cd934..7861936 100644 --- a/.ai-work/02-extract-shared-dto-event-types.md +++ b/.ai-work/02-extract-shared-dto-event-types.md @@ -1,5 +1,5 @@ # 02. Extract shared DTO/event types -Status: pending +Status: completed Move duplicated API/session DTO definitions into shared modules used by both client and server. diff --git a/src/client/src/api.ts b/src/client/src/api.ts index 77e5e44..bbe01c4 100644 --- a/src/client/src/api.ts +++ b/src/client/src/api.ts @@ -1,134 +1,6 @@ -export interface Project { - id: string; - name: string; - path: string; - createdAt: string; -} +import type { CommandOption, CommandResult, FileContentResponse, FileSuggestion, FileTreeEntry, FileTreeResponse, GitDiffResponse, GitFileState, GitStatusFile, GitStatusResponse, MessagePage, Project, SessionInfo, SessionStatus, SlashCommand, Workspace } from "../../shared/apiTypes"; -export interface Workspace { - id: string; - projectId: string; - path: string; - label: string; - branch?: string; - isMain: boolean; - isGitWorktree: boolean; -} - -export interface SessionInfo { - id: string; - path: string; - cwd: string; - name?: string; - created: string; - modified: string; - messageCount: number; - firstMessage: string; - archived?: boolean; - archivedAt?: string; -} - -export interface SessionActivity { - sessionId: string; - phase: "active" | "idle" | "error"; - label: string; - detail?: string; - at: string; -} - -export interface SessionStatus { - sessionId: string; - model?: { provider?: string; id?: string; name?: string; contextWindow?: number; reasoning?: unknown }; - thinkingLevel?: string; - isStreaming: boolean; - isCompacting: boolean; - isBashRunning: boolean; - pendingMessageCount: 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 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 interface FileContentResponse { - path: string; - language?: 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 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 } - | { type: "select"; requestId: string; title: string; options: CommandOption[] } - | { type: "unsupported"; message: string }; +export type { CommandOption, CommandResult, FileContentResponse, FileSuggestion, FileTreeEntry, FileTreeResponse, GitDiffResponse, GitFileState, GitStatusFile, GitStatusResponse, MessagePage, Project, SessionActivity, SessionInfo, SessionStatus, SlashCommand, SessionUiEvent, Workspace } from "../../shared/apiTypes"; async function request(url: string, parse: (value: unknown) => T, init?: RequestInit): Promise { const headers = new Headers(init?.headers); diff --git a/src/client/src/sessionSocket.ts b/src/client/src/sessionSocket.ts index 016667f..d378dc4 100644 --- a/src/client/src/sessionSocket.ts +++ b/src/client/src/sessionSocket.ts @@ -1,16 +1,7 @@ -import { globalSessionEvents, sessionEvents, type SessionActivity, type SessionStatus } from "./api"; +import { globalSessionEvents, sessionEvents } from "./api"; +import type { SessionUiEvent } from "../../shared/apiTypes"; -export type SessionUiEvent = - | { type: "assistant.delta"; text: string } - | { type: "tool.start"; toolName: string; summary: string; args?: unknown } - | { type: "tool.end"; toolName: string; text: string; isError: boolean; content?: 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: "status.update"; status: SessionStatus } - | { type: "activity.update"; activity: SessionActivity } - | { type: "command.output"; level: "info" | "success" | "error"; message: string } - | { type: "session.error"; message: string }; +export type { SessionUiEvent } from "../../shared/apiTypes"; export class SessionSocket { private socket: WebSocket | undefined; diff --git a/src/server/git/gitService.ts b/src/server/git/gitService.ts index 734c119..ba864a8 100644 --- a/src/server/git/gitService.ts +++ b/src/server/git/gitService.ts @@ -1,34 +1,8 @@ import { createHash } from "node:crypto"; import { spawn } from "node:child_process"; +import type { GitDiffResponse, GitFileState, GitStatusFile, GitStatusResponse } from "../../shared/apiTypes.js"; import { normalizeRelativePath } from "../workspaces/pathSafety.js"; -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; -} - const MAX_OUTPUT = 2 * 1024 * 1024; export async function gitStatus(cwd: string): Promise { diff --git a/src/server/types.ts b/src/server/types.ts index e0ab9e4..1bb2024 100644 --- a/src/server/types.ts +++ b/src/server/types.ts @@ -1,70 +1,13 @@ -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; - isGitWorktree: boolean; -} - -export interface ClientSession { - id: string; - path: string; - cwd: string; - name?: string; - created: string; - modified: string; - messageCount: number; - firstMessage: string; - archived?: boolean; - archivedAt?: string; -} - -export interface ClientMessagePage { - messages: unknown[]; - start: number; - total: number; -} - -export interface ClientSessionStatus { - sessionId: string; - model?: { provider?: string; id?: string; name?: string; contextWindow?: number; reasoning?: unknown }; - thinkingLevel?: string; - isStreaming: boolean; - isCompacting: boolean; - isBashRunning: boolean; - pendingMessageCount: 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 ClientCommand { - name: string; - description?: string; - source: "extension" | "prompt" | "skill" | "builtin"; -} - -export interface ClientFileSuggestion { - path: string; - kind: "tracked" | "untracked" | "other"; -} - -export interface ClientCommandOption { - value: string; - label: string; - description?: string; -} - -export type ClientCommandResult = - | { type: "done"; message?: string; session?: ClientSession } - | { type: "select"; requestId: string; title: string; options: ClientCommandOption[] } - | { type: "unsupported"; message: string }; +export type { + Project, + Workspace, + SessionInfo as ClientSession, + MessagePage as ClientMessagePage, + SessionStatus as ClientSessionStatus, + SlashCommand as ClientCommand, + FileSuggestion as ClientFileSuggestion, + CommandOption as ClientCommandOption, + CommandResult as ClientCommandResult, + SessionActivity as ClientSessionActivity, + SessionUiEvent, +} from "../shared/apiTypes.js"; diff --git a/src/server/workspaces/fileContentService.ts b/src/server/workspaces/fileContentService.ts index 983c696..96ff86f 100644 --- a/src/server/workspaces/fileContentService.ts +++ b/src/server/workspaces/fileContentService.ts @@ -1,17 +1,7 @@ import { readFile, stat } from "node:fs/promises"; +import type { FileContentResponse } from "../../shared/apiTypes.js"; import { resolveInsideWorkspace } from "./pathSafety.js"; -export interface FileContentResponse { - path: string; - language?: string; - encoding: "utf8"; - size: number; - modifiedAt: string; - content: string; - truncated: boolean; - binary: boolean; -} - const MAX_BYTES = 512 * 1024; export async function readWorkspaceFile(rootPath: string, path: string | undefined): Promise { diff --git a/src/server/workspaces/fileTreeService.ts b/src/server/workspaces/fileTreeService.ts index b12cbe5..6e62bde 100644 --- a/src/server/workspaces/fileTreeService.ts +++ b/src/server/workspaces/fileTreeService.ts @@ -1,22 +1,8 @@ import { lstat, readdir } from "node:fs/promises"; import { join } from "node:path"; +import type { FileTreeEntry, FileTreeResponse } from "../../shared/apiTypes.js"; import { resolveInsideWorkspace } from "./pathSafety.js"; -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; -} - const MAX_ENTRIES = 1000; export async function listWorkspaceTree(rootPath: string, path: string | undefined): Promise { diff --git a/src/shared/apiTypes.ts b/src/shared/apiTypes.ts new file mode 100644 index 0000000..fb2b77c --- /dev/null +++ b/src/shared/apiTypes.ts @@ -0,0 +1,143 @@ +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; + isGitWorktree: boolean; +} + +export interface SessionInfo { + id: string; + path: string; + cwd: string; + name?: string; + created: string; + modified: string; + messageCount: number; + firstMessage: string; + archived?: boolean; + archivedAt?: string; +} + +export interface SessionActivity { + sessionId: string; + phase: "active" | "idle" | "error"; + label: string; + detail?: string; + at: string; +} + +export interface SessionStatus { + sessionId: string; + model?: { provider?: string; id?: string; name?: string; contextWindow?: number; reasoning?: unknown }; + thinkingLevel?: string; + isStreaming: boolean; + isCompacting: boolean; + isBashRunning: boolean; + pendingMessageCount: 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 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 interface FileContentResponse { + path: string; + language?: 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 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 } + | { type: "select"; requestId: string; title: string; options: CommandOption[] } + | { type: "unsupported"; message: string }; + +export type SessionUiEvent = + | { type: "assistant.delta"; text: string } + | { type: "tool.start"; toolName: string; summary: string; args?: unknown } + | { type: "tool.end"; toolName: string; text: string; isError: boolean; content?: 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: "status.update"; status: SessionStatus } + | { type: "activity.update"; activity: SessionActivity } + | { type: "command.output"; level: "info" | "success" | "error"; message: string } + | { type: "session.error"; message: string };