From d6f97065c16274a0e63bb2e275cd93cd7c43c7bc Mon Sep 17 00:00:00 2001 From: weishu Date: Thu, 21 May 2026 10:31:50 +0800 Subject: [PATCH] Share REST and RPC response types --- cli/src/api/apiMachine.ts | 23 +--- cli/src/modules/common/codexModels.ts | 15 +-- .../modules/common/handlers/directories.ts | 14 +-- cli/src/modules/common/handlers/files.ts | 15 +-- cli/src/modules/common/handlers/git.ts | 9 +- cli/src/modules/common/handlers/uploads.ts | 12 +-- cli/src/modules/common/opencodeModels.ts | 13 +-- hub/src/sync/rpcGateway.ts | 101 +++++------------- shared/package.json | 1 + shared/src/apiTypes.ts | 95 ++++++++++++++++ shared/src/index.ts | 1 + web/src/api/client.ts | 20 ++-- web/src/types/api.ts | 97 ++++------------- 13 files changed, 166 insertions(+), 250 deletions(-) create mode 100644 shared/src/apiTypes.ts diff --git a/cli/src/api/apiMachine.ts b/cli/src/api/apiMachine.ts index dc2a5054..340b14fa 100644 --- a/cli/src/api/apiMachine.ts +++ b/cli/src/api/apiMachine.ts @@ -9,6 +9,7 @@ import { basename, dirname, isAbsolute, join, relative, resolve as resolvePath } import { logger } from '@/ui/logger' import { configuration } from '@/configuration' import type { Update, UpdateMachineBody } from '@hapi/protocol' +import type { MachineDirectoryEntry, MachineListDirectoryResponse, PathExistsResponse } from '@hapi/protocol/apiTypes' import type { RunnerState, Machine, MachineMetadata } from './types' import { RunnerStateSchema, MachineMetadataSchema } from './types' import { backoff } from '@/utils/time' @@ -68,28 +69,10 @@ interface PathExistsRequest { paths: string[] } -interface PathExistsResponse { - exists: Record -} - interface ListMachineDirectoryRequest { path: string } -interface ListMachineDirectoryEntry { - name: string - type: 'file' | 'directory' | 'other' - size?: number - modified?: number - isGitRepo?: boolean -} - -interface ListMachineDirectoryResponse { - success: boolean - entries?: ListMachineDirectoryEntry[] - error?: string -} - function normalizeWorkspaceRoots(paths?: string[]): string[] | undefined { if (!paths?.length) { return undefined @@ -163,7 +146,7 @@ export class ApiMachineClient { return { exists } }) - this.rpcHandlerManager.registerHandler('list-directory', async (params) => { + this.rpcHandlerManager.registerHandler('list-directory', async (params) => { if (!this.normalizedWorkspaceRoots?.length) { return { success: false, error: 'Workspace browsing is not enabled for this machine' } } @@ -185,7 +168,7 @@ export class ApiMachineClient { } const dirEntries = await readdir(targetPath, { withFileTypes: true }) - const entries: ListMachineDirectoryEntry[] = [] + const entries: MachineDirectoryEntry[] = [] await Promise.all(dirEntries.map(async (entry) => { if (entry.name.startsWith('.')) return diff --git a/cli/src/modules/common/codexModels.ts b/cli/src/modules/common/codexModels.ts index 800c74f8..dadb4089 100644 --- a/cli/src/modules/common/codexModels.ts +++ b/cli/src/modules/common/codexModels.ts @@ -1,23 +1,12 @@ +import type { CodexModelsResponse, CodexModelSummary } from '@hapi/protocol/apiTypes'; import { CodexAppServerClient } from '@/codex/codexAppServerClient'; import { getErrorMessage } from './rpcResponses'; -export interface CodexModelSummary { - id: string; - displayName: string; - isDefault: boolean; - defaultReasoningEffort?: string | null; - supportedReasoningEfforts?: string[]; -} - export interface ListCodexModelsRequest { includeHidden?: boolean; } -export interface ListCodexModelsResponse { - success: boolean; - models?: CodexModelSummary[]; - error?: string; -} +export type ListCodexModelsResponse = CodexModelsResponse; function asNonEmptyString(value: unknown): string | null { return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null; diff --git a/cli/src/modules/common/handlers/directories.ts b/cli/src/modules/common/handlers/directories.ts index e8fd176b..fb21d8f2 100644 --- a/cli/src/modules/common/handlers/directories.ts +++ b/cli/src/modules/common/handlers/directories.ts @@ -1,6 +1,7 @@ import { logger } from '@/ui/logger' import { readdir, stat } from 'fs/promises' import { basename, join, resolve } from 'path' +import type { DirectoryEntry, ListDirectoryResponse } from '@hapi/protocol/apiTypes' import type { RpcHandlerManager } from '@/api/rpc/RpcHandlerManager' import { validatePath } from '../pathSecurity' import { getErrorMessage, rpcError } from '../rpcResponses' @@ -9,19 +10,6 @@ interface ListDirectoryRequest { path: string } -interface DirectoryEntry { - name: string - type: 'file' | 'directory' | 'other' - size?: number - modified?: number -} - -interface ListDirectoryResponse { - success: boolean - entries?: DirectoryEntry[] - error?: string -} - interface GetDirectoryTreeRequest { path: string maxDepth: number diff --git a/cli/src/modules/common/handlers/files.ts b/cli/src/modules/common/handlers/files.ts index 8cf8a5e8..d8f34a4c 100644 --- a/cli/src/modules/common/handlers/files.ts +++ b/cli/src/modules/common/handlers/files.ts @@ -2,6 +2,7 @@ import { logger } from '@/ui/logger' import { readFile, stat, writeFile } from 'fs/promises' import { createHash } from 'crypto' import { resolve } from 'path' +import type { FileReadResponse, GeneratedImageResponse } from '@hapi/protocol/apiTypes' import type { RpcHandlerManager } from '@/api/rpc/RpcHandlerManager' import { validatePath } from '../pathSecurity' import { getGeneratedImage } from '../generatedImages' @@ -11,23 +12,13 @@ interface ReadFileRequest { path: string } -interface ReadFileResponse { - success: boolean - content?: string - error?: string -} +type ReadFileResponse = FileReadResponse interface ReadGeneratedImageRequest { id: string } -interface ReadGeneratedImageResponse { - success: boolean - content?: string - mimeType?: string - fileName?: string - error?: string -} +type ReadGeneratedImageResponse = GeneratedImageResponse interface WriteFileRequest { path: string diff --git a/cli/src/modules/common/handlers/git.ts b/cli/src/modules/common/handlers/git.ts index d8583708..ba280edd 100644 --- a/cli/src/modules/common/handlers/git.ts +++ b/cli/src/modules/common/handlers/git.ts @@ -1,5 +1,6 @@ import { execFile, type ExecFileOptions } from 'child_process' import { promisify } from 'util' +import type { CommandResponse } from '@hapi/protocol/apiTypes' import type { RpcHandlerManager } from '@/api/rpc/RpcHandlerManager' import { validatePath } from '../pathSecurity' import { rpcError } from '../rpcResponses' @@ -24,13 +25,7 @@ interface GitDiffFileRequest { timeout?: number } -interface GitCommandResponse { - success: boolean - stdout?: string - stderr?: string - exitCode?: number - error?: string -} +type GitCommandResponse = CommandResponse function resolveCwd(requestedCwd: string | undefined, workingDirectory: string): { cwd: string; error?: string } { const cwd = requestedCwd ?? workingDirectory diff --git a/cli/src/modules/common/handlers/uploads.ts b/cli/src/modules/common/handlers/uploads.ts index acd67f2d..c5c089fc 100644 --- a/cli/src/modules/common/handlers/uploads.ts +++ b/cli/src/modules/common/handlers/uploads.ts @@ -2,6 +2,7 @@ import { logger } from '@/ui/logger' import { mkdir, mkdtemp, rm, writeFile } from 'fs/promises' import { join, resolve, sep } from 'path' import { rmSync } from 'node:fs' +import type { DeleteUploadResponse, UploadFileResponse } from '@hapi/protocol/apiTypes' import type { RpcHandlerManager } from '@/api/rpc/RpcHandlerManager' import { getErrorMessage, rpcError } from '../rpcResponses' import { getHapiBlobsDir } from '@/constants/uploadPaths' @@ -13,22 +14,11 @@ interface UploadFileRequest { mimeType: string } -interface UploadFileResponse { - success: boolean - path?: string - error?: string -} - interface DeleteUploadRequest { sessionId?: string path: string } -interface DeleteUploadResponse { - success: boolean - error?: string -} - const uploadDirs = new Map() const uploadDirPromises = new Map>() const uploadDirCleanupRequested = new Set() diff --git a/cli/src/modules/common/opencodeModels.ts b/cli/src/modules/common/opencodeModels.ts index 79902b4b..5e8fc4c2 100644 --- a/cli/src/modules/common/opencodeModels.ts +++ b/cli/src/modules/common/opencodeModels.ts @@ -1,23 +1,14 @@ import { asString, isObject } from '@hapi/protocol'; +import type { OpencodeModelsResponse, OpencodeModelSummary } from '@hapi/protocol/apiTypes'; import { AcpStdioTransport } from '@/agent/backends/acp/AcpStdioTransport'; import packageJson from '../../../package.json'; import { getErrorMessage } from './rpcResponses'; -export interface OpencodeModelSummary { - modelId: string; - name?: string; -} - export interface ListOpencodeModelsForCwdRequest { cwd?: string; } -export interface ListOpencodeModelsForCwdResponse { - success: boolean; - availableModels?: OpencodeModelSummary[]; - currentModelId?: string | null; - error?: string; -} +export type ListOpencodeModelsForCwdResponse = OpencodeModelsResponse; interface CacheEntry { expiresAt: number; diff --git a/hub/src/sync/rpcGateway.ts b/hub/src/sync/rpcGateway.ts index a13fb2e5..f57b0cf9 100644 --- a/hub/src/sync/rpcGateway.ts +++ b/hub/src/sync/rpcGateway.ts @@ -1,85 +1,36 @@ import type { AgentFlavor, CodexCollaborationMode, PermissionMode } from '@hapi/protocol/types' +import type { + CodexModelSummary, + CodexModelsResponse, + CommandResponse, + DeleteUploadResponse, + DirectoryEntry, + FileReadResponse, + GeneratedImageResponse, + ListDirectoryResponse, + OpencodeModelsResponse, + OpencodeModelSummary, + PathExistsResponse, + UploadFileResponse +} from '@hapi/protocol/apiTypes' import type { Server } from 'socket.io' import type { RpcRegistry } from '../socket/rpcRegistry' const DEFAULT_RPC_TIMEOUT_MS = 30_000 const MODEL_LIST_RPC_TIMEOUT_MS = 120_000 -export type RpcCommandResponse = { - success: boolean - stdout?: string - stderr?: string - exitCode?: number - error?: string -} - -export type RpcReadFileResponse = { - success: boolean - content?: string - error?: string -} - -export type RpcGeneratedImageResponse = { - success: boolean - content?: string - mimeType?: string - fileName?: string - error?: string -} - -export type RpcUploadFileResponse = { - success: boolean - path?: string - error?: string -} - -export type RpcDeleteUploadResponse = { - success: boolean - error?: string -} - -export type RpcDirectoryEntry = { - name: string - type: 'file' | 'directory' | 'other' - size?: number - modified?: number -} - -export type RpcListDirectoryResponse = { - success: boolean - entries?: RpcDirectoryEntry[] - error?: string -} - -export type RpcPathExistsResponse = { - exists: Record -} - -export type RpcCodexModel = { - id: string - displayName: string - isDefault: boolean - defaultReasoningEffort?: string | null - supportedReasoningEfforts?: string[] -} - -export type RpcListCodexModelsResponse = { - success: boolean - models?: RpcCodexModel[] - error?: string -} - -export type RpcOpencodeModel = { - modelId: string - name?: string -} - -export type RpcListOpencodeModelsResponse = { - success: boolean - availableModels?: RpcOpencodeModel[] - currentModelId?: string | null - error?: string -} +export type RpcCommandResponse = CommandResponse +export type RpcReadFileResponse = FileReadResponse +export type RpcGeneratedImageResponse = GeneratedImageResponse +export type RpcUploadFileResponse = UploadFileResponse +export type RpcDeleteUploadResponse = DeleteUploadResponse +export type RpcDirectoryEntry = DirectoryEntry +export type RpcListDirectoryResponse = ListDirectoryResponse +export type RpcPathExistsResponse = PathExistsResponse +export type RpcCodexModel = CodexModelSummary +export type RpcListCodexModelsResponse = CodexModelsResponse +export type RpcOpencodeModel = OpencodeModelSummary +export type RpcListOpencodeModelsResponse = OpencodeModelsResponse export class RpcGateway { constructor( diff --git a/shared/package.json b/shared/package.json index 85fa2e53..0254c0a1 100644 --- a/shared/package.json +++ b/shared/package.json @@ -7,6 +7,7 @@ "types": "./src/index.ts", "exports": { ".": "./src/index.ts", + "./apiTypes": "./src/apiTypes.ts", "./messages": "./src/messages.ts", "./buildInfo": "./src/buildInfo.ts", "./modes": "./src/modes.ts", diff --git a/shared/src/apiTypes.ts b/shared/src/apiTypes.ts new file mode 100644 index 00000000..4b9d9cc9 --- /dev/null +++ b/shared/src/apiTypes.ts @@ -0,0 +1,95 @@ +export type CommandResponse = { + success: boolean + stdout?: string + stderr?: string + exitCode?: number + error?: string +} + +export type GitCommandResponse = CommandResponse + +export type FileReadResponse = { + success: boolean + content?: string + error?: string +} + +export type GeneratedImageResponse = { + success: boolean + content?: string + mimeType?: string + fileName?: string + error?: string +} + +export type UploadFileResponse = { + success: boolean + path?: string + error?: string +} + +export type DeleteUploadResponse = { + success: boolean + error?: string +} + +export type DirectoryEntry = { + name: string + type: 'file' | 'directory' | 'other' + size?: number + modified?: number +} + +export type ListDirectoryResponse = { + success: boolean + entries?: DirectoryEntry[] + error?: string +} + +export type RpcListDirectoryResponse = ListDirectoryResponse + +export type MachineDirectoryEntry = DirectoryEntry & { + isGitRepo?: boolean +} + +export type MachineListDirectoryResponse = { + success: boolean + entries?: MachineDirectoryEntry[] + error?: string +} + +export type PathExistsResponse = { + exists: Record +} + +export type MachinePathsExistsResponse = PathExistsResponse + +export type CodexModelSummary = { + id: string + displayName: string + isDefault: boolean + defaultReasoningEffort?: string | null + supportedReasoningEfforts?: string[] +} + +export type CodexModelsResponse = { + success: boolean + models?: CodexModelSummary[] + error?: string +} + +export type ListCodexModelsResponse = CodexModelsResponse + +export type OpencodeModelSummary = { + modelId: string + name?: string +} + +export type OpencodeModelsResponse = { + success: boolean + availableModels?: OpencodeModelSummary[] + currentModelId?: string | null + error?: string +} + +export type ListOpencodeModelsResponse = OpencodeModelsResponse diff --git a/shared/src/index.ts b/shared/src/index.ts index a21a600a..9716e60d 100644 --- a/shared/src/index.ts +++ b/shared/src/index.ts @@ -1,3 +1,4 @@ +export * from './apiTypes' export * from './messages' export * from './buildInfo' export * from './flavors' diff --git a/web/src/api/client.ts b/web/src/api/client.ts index 883e555a..52c25619 100644 --- a/web/src/api/client.ts +++ b/web/src/api/client.ts @@ -2,17 +2,9 @@ import type { AttachmentMetadata, AuthResponse, CodexCollaborationMode, - DeleteUploadResponse, - ListDirectoryResponse, - FileReadResponse, FileSearchResponse, - GitCommandResponse, - MachineListDirectoryResponse, - MachinePathsExistsResponse, MachinesResponse, MessagesResponse, - CodexModelsResponse, - OpencodeModelsResponse, PermissionMode, PushSubscriptionPayload, PushUnsubscribePayload, @@ -20,11 +12,21 @@ import type { SlashCommandsResponse, SkillsResponse, SpawnResponse, - UploadFileResponse, VisibilityPayload, SessionResponse, SessionsResponse } from '@/types/api' +import type { + CodexModelsResponse, + DeleteUploadResponse, + FileReadResponse, + GitCommandResponse, + ListDirectoryResponse, + MachineListDirectoryResponse, + MachinePathsExistsResponse, + OpencodeModelsResponse, + UploadFileResponse +} from '@hapi/protocol/apiTypes' import type { AgentFlavor } from '@hapi/protocol' import type { CancelMessageResponse } from '@hapi/protocol/schemas' diff --git a/web/src/types/api.ts b/web/src/types/api.ts index f3b6c6d7..8ae9c6f9 100644 --- a/web/src/types/api.ts +++ b/web/src/types/api.ts @@ -6,6 +6,24 @@ import type { WorktreeMetadata } from '@hapi/protocol/types' +export type { + CodexModelsResponse, + CodexModelSummary, + CommandResponse, + DeleteUploadResponse, + DirectoryEntry, + FileReadResponse, + GitCommandResponse, + ListDirectoryResponse, + MachineDirectoryEntry, + MachineListDirectoryResponse, + MachinePathsExistsResponse, + OpencodeModelsResponse, + OpencodeModelSummary, + PathExistsResponse, + UploadFileResponse +} from '@hapi/protocol/apiTypes' + export type { AgentState, AttachmentMetadata, @@ -100,34 +118,11 @@ export type MessagesResponse = { } export type MachinesResponse = { machines: Machine[] } -export type MachinePathsExistsResponse = { exists: Record } - -export type MachineDirectoryEntry = { - name: string - type: 'file' | 'directory' | 'other' - size?: number - modified?: number - isGitRepo?: boolean -} - -export type MachineListDirectoryResponse = { - success: boolean - entries?: MachineDirectoryEntry[] - error?: string -} export type SpawnResponse = | { type: 'success'; sessionId: string } | { type: 'error'; message: string } -export type GitCommandResponse = { - success: boolean - stdout?: string - stderr?: string - exitCode?: number - error?: string -} - export type FileSearchItem = { fileName: string filePath: string @@ -141,36 +136,6 @@ export type FileSearchResponse = { error?: string } -export type DirectoryEntry = { - name: string - type: 'file' | 'directory' | 'other' - size?: number - modified?: number -} - -export type ListDirectoryResponse = { - success: boolean - entries?: DirectoryEntry[] - error?: string -} - -export type FileReadResponse = { - success: boolean - content?: string - error?: string -} - -export type UploadFileResponse = { - success: boolean - path?: string - error?: string -} - -export type DeleteUploadResponse = { - success: boolean - error?: string -} - export type GitFileStatus = { fileName: string filePath: string @@ -215,32 +180,6 @@ export type SkillsResponse = { error?: string } -export type CodexModelSummary = { - id: string - displayName: string - isDefault: boolean - defaultReasoningEffort?: string | null - supportedReasoningEfforts?: string[] -} - -export type CodexModelsResponse = { - success: boolean - models?: CodexModelSummary[] - error?: string -} - -export type OpencodeModelSummary = { - modelId: string - name?: string -} - -export type OpencodeModelsResponse = { - success: boolean - availableModels?: OpencodeModelSummary[] - currentModelId?: string | null - error?: string -} - export type PushSubscriptionKeys = { p256dh: string auth: string