UNPKG

@builder.io/dev-tools

Version:

Builder.io Visual CMS Devtools

787 lines (786 loc) 36 kB
import type { DevToolsSys } from "../types"; import { type Credentials } from "./credentials"; import { TimelineCollector } from "./utils/timeline-collector"; import type { CodegenFeedback, CodeGenToolMap, CodegenTurn, CustomInstruction, FusionConfig, GenerateCompletionState, GenerateCompletionStep, GenerateUserMessage, SessionMode, UserContext, UserSource, WorkspaceFolder, LoadWholeSessionOptions, LoadWholeSessionResult, LoadHistoryResult, ApplyActionsResult, PrivacyMode, CodeGenPosition, BackupGitRepoResult, SuggestedActionBuildError, PushChangesArgs, CodegenApiResult, CodegenApiTerminal, ConfigureDevOrchestratorOpts, ConfigureDevOrchestratorUpdates, RepoMetrics, FolderWatchEvent, MCPClientStatus, MCPServerConfig, CodegenApiCreateTerminal, SyncChangesFromRemote, SearchFilesOptions, SearchFilesResult, SearchFileTreeOptions, SearchFileTreeResult, HttpServerState, ExplorationMetadataToolInput, CodegenAbortOptions, MessageUpdateOptions, GitRepoContext, SystemReminderObj, GitSnapshot, AutoPushMode, QueueMode, CustomAgentDefinition, AgentModelOverrides, MemorySummary, SendMessageToOrgAgentInput, ReasoningEffort } from "#ai-utils"; import { type ToolResolution } from "./code-tools"; import { type SubAgent } from "./utils/agent-discovery"; import EventEmitter from "node:events"; import { type McpScope } from "./mcp-manage"; import { type RunCommandOptions } from "./utils/git"; import { type DevServerOrchestrator } from "./launch/dev-server-orchestrator"; import type { FusionStatusMonitor } from "./launch/machine-health"; export interface SyncChangesFromBranches { canPush: boolean; branches: string[]; uncommittedChanges: "stash" | "commit" | "fail"; allowUnrelatedHistory: boolean; fastForward: "never" | "required" | "auto"; requestRefresh?: boolean; /** * When true, compute the remote branch per-repo using #getRemoteBranch(ctx) * instead of using the passed branches array. This handles multi-repo * scenarios where each repo may have a different feature branch. */ syncRemoteBranch?: boolean; /** * When true, perform a hard reset to the remote feature/base branch instead * of merging. The `branches` array must contain the feature/base branch name. */ resetToBase?: boolean; /** * Whether to update the internal "last AI commits" baseline after the sync * completes. Defaults to `true`. When `false`, `#lastAICommits` is left * untouched so `getChangesReport()` keeps diffing from the previous * baseline (useful to surface what changed since the reset in a prompt). */ updateLastCommits?: boolean; } export interface SessionContext { sessionId: string; turns: CodegenTurn[]; customInstructions: CustomInstruction[]; customAgents: CustomAgentDefinition[]; userContext: UserContext; prettierConfig: Record<string, unknown> | null; state: GenerateCompletionState; title: string | undefined; beforeCommit: GitSnapshot | undefined; createdUnixTime: number; updatedUnixTime: number; canLoadMore: boolean; sessionMode: SessionMode; } export interface CodeGenSessionOptionsBase { sys: DevToolsSys; credentials: Credentials; position: CodeGenPosition; maxTokens?: number; queueMode?: QueueMode; /** * Default {@link SyncChangesFromRemote} policy applied to user messages * that arrive without an explicit `syncChanges` field. Typically set when * spawning a sub-agent whose definition declares `defaultSyncChanges` * (e.g. the setup-project agent uses this to reset to base before each * message). `undefined` keeps the historical no-op behavior. */ defaultSyncChanges?: SyncChangesFromRemote; privacyMode?: PrivacyMode; softContextWindow?: number; builtInCustomInstructions?: CustomInstruction[]; builtInCustomAgents?: SubAgent[]; builtInMCPServerConfig?: MCPServerConfig; autoImportLocalMCPs?: boolean; systemPromptOverride?: string | string[]; fusionConfig?: FusionConfig; devServerOrchestrator?: DevServerOrchestrator; git?: boolean; gitAutoInit?: boolean; workingDirectory?: string; mcpServers?: boolean; enabledTools?: (keyof CodeGenToolMap)[]; modelOverride?: string; agentModelOverrides?: AgentModelOverrides; skipFileDiff?: boolean; agentType?: string; includeMemories?: boolean; fusionStatusMonitor?: FusionStatusMonitor; asyncSubAgents?: boolean; persistSessionLocally?: boolean; /** Default reasoning effort level for this session. Sent to the service with each completion request. */ reasoning?: ReasoningEffort; /** Immediate parent session id when this is a sub-agent. */ parentSessionId?: string; /** Root session of the agent tree. Inherited by every descendant; cost rolls up here in one hop. */ mainSessionId?: string; } export interface CodeGenSessionOptionsSession extends CodeGenSessionOptionsBase { sessionOrCompletionId?: string; } export interface CodeGenSessionOptionsInitialUrl extends CodeGenSessionOptionsBase { initialUrl: string; } export type CodeGenSessionOptions = CodeGenSessionOptionsSession | CodeGenSessionOptionsInitialUrl; export type SpawnAgentResult = SpawnAgentResultSuccess | SpawnAgentResultFailed; export interface SpawnAgentResultSuccess { success: boolean; response: string; metadata: ExplorationMetadataToolInput; lastTurn?: CodegenTurn; sessionId: string; } export interface SpawnAgentResultFailed { success: false; response: string; } export type CodeGenEventEmitter = EventEmitter<{ step: [GenerateCompletionStep]; idle: []; }>; export declare class CodeGenSession { #private; /** * Returns the primary git working directory path. * This is always #gitRepoContexts[0].path for backwards compatibility. */ get primaryGitPath(): string; constructor(options: CodeGenSessionOptions); get fusionConfig(): FusionConfig | undefined; /** Returns the first git-enabled folder for backwards compatibility */ get gitEnabledFolder(): WorkspaceFolder | undefined; /** Returns all git-enabled folders */ get gitEnabledFolders(): WorkspaceFolder[]; /** Returns runtime git repo contexts for all enabled repos */ get gitRepoContexts(): readonly GitRepoContext[]; get workingDirectory(): string; get bashWorkingDirectory(): string; getSessionMode(): SessionMode; switchSessionMode(newMode: SessionMode): Promise<void>; switchModelOverride(model: string | undefined): Promise<void>; removeSystemReminder(tag: string): void; /** * Returns memories whose glob matches the given file path, excluding ones * already injected this session. Marks returned memories as reminded. */ getMemoriesForFile(filePath: string): MemorySummary[]; queueSystemReminder(reminder: SystemReminderObj): { operation: string; }; setDefaultAutoPush(autoPush: AutoPushMode | undefined): void; setPrivacyMode(privacyMode: PrivacyMode | undefined): Promise<void>; /** * Snapshot of the current local MCP statuses (CLI-managed servers only). * Server-managed MCPs are returned by the codegen pipeline via the * `mcp-status` step; the slash command merges both maps client-side. */ getLocalMcpStatus(): Record<string, MCPClientStatus>; /** * Re-emit the merged MCP status snapshot on demand. Lets a freshly-attached * UI (e.g. the dev-server console connecting before any completion has run) * pull the current view without firing a noop completion. Server-managed * entries are whatever was last seen from the codegen pipeline; local * entries are always live. */ refreshMcpStatus(): Record<string, MCPClientStatus>; /** * Kick off OAuth for a single CLI-managed MCP. Returns immediately; the * loopback browser flow completes in the background and emits a * `local-mcp-auth-complete` step on success/failure (same path the LLM * `__authenticate` synthetic tool uses). * * For server-managed MCPs (`source === "server"`), tells the caller that * v1 only supports re-auth from the Builder.io org settings — wiring up * `POST /mcp/oauth/connect` from the CLI is deferred to v2. */ authenticateMcpServer(serverName: string): Promise<{ started: boolean; message?: string; }>; /** * Drop persisted tokens for a CLI-managed remote MCP and surface it as * `auth_required` so the UI can prompt for re-authentication. Server- * managed MCPs are out of scope in v1 — token storage is in Firestore * and requires an authenticated `DELETE /mcp/oauth/credentials` endpoint * the service doesn't expose yet. */ clearMcpAuth(serverName: string): Promise<{ cleared: boolean; message?: string; }>; /** * Re-run `connectRemoteMCP` for a single CLI-managed server. Useful after * editing `mcp.json`, clearing auth, or recovering from a transient * network failure. Server-managed MCPs reconnect fresh on every codegen * turn and have no long-lived client to bounce — `/mcp` hides the action * for them. */ reconnectMcpServer(serverName: string): Promise<{ success: boolean; toolsAdded: number; message?: string; }>; /** * Persist `disabled: true|false` to the right `mcp.json` and refresh the * status snapshot. The next manager rebuild reads the flag and either * skips the transport or reconnects; for an immediate refresh we also * mutate the in-memory client (disable only — re-enabling requires a * full reconnect on the next turn so transport state is sound). */ setMcpServerDisabled(serverName: string, disabled: boolean, scope?: McpScope): Promise<{ disabled: boolean; path: string; scope: McpScope; }>; initializeSession(opts?: { skipSessionLoading?: boolean; signal?: AbortSignal; }): Promise<void>; loadHistory(): Promise<LoadHistoryResult>; loadWholeSession(opts?: LoadWholeSessionOptions): Promise<LoadWholeSessionResult>; loadMoreTurns(): Promise<CodegenTurn[]>; setCustomInstructions(instructions: CustomInstruction[]): Promise<void>; setCustomAgents(agents: SubAgent[]): Promise<void>; pushRepoV2(repoInfo: { repoFullName: string; repoUrl: string; }): Promise<CodegenApiResult>; /** * Ensures git authentication is valid by refreshing git URLs and tokens. * This should be called before executing git/gh commands to prevent * authentication failures due to expired tokens. */ ensureGitAuth(): Promise<CodegenApiResult>; zipFolder(folderName: string): Promise<string>; archiveProject(): Promise<string>; isIdle(): boolean; explainBusy(): { isBusy: boolean; gitRunning: boolean; sessionRunning: boolean; pendingPromises: boolean; runningSubAgents: boolean; }; needsBackup(): Promise<boolean>; uploadBackup(forcedFullBackup?: boolean): Promise<BackupGitRepoResult | { success: false; error: Error; }>; getCommitMode(): import("#ai-utils").CommitMode; pushChanges(opts: PushChangesArgs): Promise<CodegenApiResult>; abortMerge(emitStatus?: boolean): Promise<CodegenApiResult>; syncChangesFromRemote(opts?: SyncChangesFromRemote): Promise<CodegenApiResult>; /** * Get the current commit hash */ getCurrentCommitHash(branchName?: string, repoPath?: string): Promise<string | undefined>; getCurrentBranch(repoPath?: string): Promise<string>; /** * Get the feature branch name */ getFeatureBranch(): string; /** * Get the AI branch name */ getAiBranch(): string; git(args: string[], opts?: string | Partial<RunCommandOptions>): Promise<string>; setDebug(debug: boolean): void; createTerminal(options?: CodegenApiCreateTerminal): Promise<CodegenApiTerminal>; updateTerminal({ terminalId, cols, rows, title }: { terminalId: string; cols?: number; rows?: number; title?: string; }): boolean; writeTerminal({ terminalId, data }: { terminalId: string; data: string; }): boolean; signalTerminal({ terminalId, signal }: { terminalId: string; signal: "SIGINT" | "SIGTERM" | "SIGKILL"; }): boolean; disposeTerminal({ terminalId, emitTerminals }: { terminalId: string; emitTerminals?: boolean; }): boolean; restartTerminal({ terminalId }: { terminalId: string; }): Promise<boolean>; subscribeTerminal({ terminalId, onData, onExit }: { terminalId: string; onData: (chunk: string) => void; onExit?: (code?: number) => void; }): (() => void) | undefined; getAllFiles(options?: { getDotFiles?: boolean; globbyPattern?: string; includePattern?: string; gitignore?: boolean; deep?: number; truncate?: number; maxFiles?: number; onlyFiles?: boolean; }): Promise<string[]>; searchFiles(options: SearchFilesOptions): Promise<SearchFilesResult>; /** * Search for files by their names/paths using Fuse.js fuzzy matching. * Matches against the full path (including directory segments), so * "components" matches "src/components/Button.tsx" — unlike the old * ripgrep glob approach which only matched the filename. * Respects access control policies (deny patterns). */ searchFileTree(options: SearchFileTreeOptions): Promise<SearchFileTreeResult>; collectRepoMetrics(opts?: { rootPath?: string; folderName?: string; }): Promise<RepoMetrics>; getSessionId(): string; /** Root session id passed to each spawned sub-agent as their mainSessionId. */ getMainSessionId(): string; updateMetadata(metadata: Record<string, any>): void; getMetadata(): Record<string, any>; setTimeline(timeline: TimelineCollector): void; getTimeline(): TimelineCollector | null; getSpaceId(): string | undefined; revertToCommitSnapshot(commitSnapshot: GitSnapshot): Promise<void>; revertToCommitHash(commitHash: string): Promise<void>; resetToCommitSnapshot(commitSnapshot: GitSnapshot, requestRefresh?: boolean): Promise<void>; resetToCommitHash(commitHash: string, requestRefresh?: boolean): Promise<void>; /** * Core function to restore the codebase to a state that matches a predicate. * This is the main function that handles both git-based and file-based restoration. * * @param predicate Function that takes a turn and its index and returns true if we should restore up to that turn * @param dryRun If true, only simulate the restoration without making changes * @returns Array of file paths that were changed */ restore({ location, predicate, revert, dryRun, forceReplay, debug }: { location: "before" | "after"; predicate: (turn: CodegenTurn | null, index: number) => boolean; dryRun?: boolean; revert?: boolean; forceReplay?: boolean; debug?: string; }): Promise<{ undone: string[] | null; message: string; }>; restoreFromCompletionId({ location, completionId, forceReplay }: { location: "before" | "after"; completionId: string; forceReplay?: boolean; }): Promise<{ undone: string[] | null; message: string; }>; restoreBeforeCompletionId(completionId: string): Promise<{ undone: string[] | null; message: string; }>; /** * Undo all changes back to the last user message */ undoLastUserMessage(dryRun?: boolean): Promise<{ undone: string[] | null; message: string; }>; getLastUserCompletionId(): string | undefined; getLastCompletionId(): string | undefined; getCurrentState(): GenerateCompletionState; getLastApplyResultsTurn(): CodegenTurn | undefined; getLastTurn(): CodegenTurn | undefined; getLastMessage(): string | undefined; getNextUrl(): string | undefined; sendFeedback(feedback: Partial<CodegenFeedback>): Promise<void>; lastTurnHasChanges(): Promise<boolean>; waitUntilState(state: GenerateCompletionState, timeout?: number): Promise<void>; getProxyConfig(): { proxyOrigin: string | undefined; proxyDefaultOrigin: string | undefined; proxyDestination: string | undefined; }; clearSession(): Promise<void>; clearMessageQueue(emitUserMessages?: boolean): Promise<void>; updateMessage(opts: MessageUpdateOptions): void; flushMessageQueue(): Promise<void>; sendMessage(message: GenerateUserMessage | GenerateUserMessage[]): Promise<string | null>; /** * Waits for the setup command to finish if it's currently running. * Returns information about the setup command state. * * @param signal - Optional abort signal * @returns Object with setup command state information */ waitForSetupCommand(signal?: AbortSignal): Promise<{ state: "installed" | "not-installed" | "install-failed" | "install-aborted"; shouldProceed: boolean; }>; waitForDevServer(signal?: AbortSignal, timeoutMs?: number, expectedStates?: HttpServerState[]): Promise<{ shouldProceed: boolean; message: string; serverUrl?: string; }>; getNamedAgentConfig(agentName: string | undefined, sessionId: string, signal?: AbortSignal): Promise<{ success: true; options: CodeGenSessionOptions; resetAfterRun: boolean; subagent_type: string; /** Set when the run was launched with browser session replay enabled. */ replayId?: string; /** * HTTP(S) URL to force-close the browser-service WebSocket on * agent shutdown/abort. Present whenever a Chrome connection * was acquired (not just when replay is enabled) so the pool * slot is freed promptly instead of sitting idle for 30min. */ browserDisconnectUrl?: string; /** Optional override for the maximum timeout for this agent. */ maxTimeoutMs?: number; } | { success: false; response: string; }>; /** * Spawn a named custom agent by ID or name * @param agentNameOrId - The agent's name or ID * @param options - Additional spawning options * @returns SpawnAgentResult */ spawnNamedAgent(agentName: string | undefined, options: { prompt: string; user: UserSource; signal: AbortSignal; maxCompletions: number; sessionId: string; resume?: string; mcpServers?: boolean; attachmentUrls?: string[]; }): Promise<SpawnAgentResult>; /** * Returns info about currently running background sub-agents. */ getRunningSubAgents(): Array<{ id: string; subagentType: string; description: string; startedAt: number; resumed: boolean; turns: number; state: GenerateCompletionState | "unknown"; toolsRunning: boolean; }>; canHandleTool(toolName: keyof CodeGenToolMap): boolean; /** * Declare tools that are handleable at the session level, independent of * live streaming connections. Used by fire-and-forget (queue-based) * integrations like Slack where tool calls are handled asynchronously * via event hooks rather than a persistent connection. * * These capabilities are automatically cleared when the session goes idle, * simulating a "disconnect". The next message from the integration will * re-register them. */ setSessionCanHandleTools(tools: (keyof CodeGenToolMap)[]): void; clearSessionCanHandleTools(): void; setProxyOrigin(proxySrc: string | undefined, proxyDefaultOrigin: string | undefined): void; getTurns(): CodegenTurn[]; getSessionContext(): SessionContext; runSetupCommand(): Promise<import("#ai-utils").SetupCommandResult | undefined>; abortSetupCommand(): Promise<import("#ai-utils").SetupCommandResult | undefined> | undefined; abortValidateCommand(): Promise<import("#ai-utils").ValidateCommandResult | undefined> | undefined; toolsRunning(): boolean; abortAllTools(): void; /** * Fulfil a pending tool call (usually AskUser or any passThrough tool) * Exposed via websocket as `toolFullfilment` for the Builder UI to send back * the user's response. */ toolFullfilment(id: string, result: ToolResolution | string): boolean; cancelAllPendingToolFulfilments(userMessage?: string): void; fulfillToolCall(id: string, result: ToolResolution): boolean; abortToolCall(id: string, reason?: string): boolean; acceptCode(): Promise<void>; abort(abortOptions?: CodegenAbortOptions | boolean): Promise<boolean>; stopEventLoop(): Promise<void>; requestRefresh(): void; configureDevOrchestrator(opts: ConfigureDevOrchestratorOpts): Promise<ConfigureDevOrchestratorUpdates>; /** * Subscribe to file change events for the entire working directory. * Returns a dispose function to unsubscribe. * The watcher is lazily initialized on first subscription and cleaned up on last unsubscribe. */ subscribeToFileChanges(onEvent: (event: FolderWatchEvent) => void): () => Promise<void>; tsServerInit(): Promise<{ ok: boolean; cwd?: string; error?: string; }>; tsServerOpenFile(filePath: string, content: string): Promise<void>; tsServerChangeFile(filePath: string, content: string): Promise<void>; tsServerCloseFile(filePath: string): Promise<void>; tsServerGetCompletions(filePath: string, line: number, offset: number, options?: { triggerCharacter?: string; includeExternalModuleExports?: boolean; }): Promise<import("./tsserver-manager").TsServerRequestResult>; tsServerGetCompletionDetails(filePath: string, line: number, offset: number, entryNames: string[]): Promise<import("./tsserver-manager").TsServerRequestResult>; tsServerGetDefinition(filePath: string, line: number, offset: number): Promise<import("./tsserver-manager").TsServerRequestResult>; tsServerGetTypeDefinition(filePath: string, line: number, offset: number): Promise<import("./tsserver-manager").TsServerRequestResult>; tsServerGetQuickInfo(filePath: string, line: number, offset: number): Promise<import("./tsserver-manager").TsServerRequestResult>; tsServerGetReferences(filePath: string, line: number, offset: number): Promise<import("./tsserver-manager").TsServerRequestResult>; tsServerGetSignatureHelp(filePath: string, line: number, offset: number): Promise<import("./tsserver-manager").TsServerRequestResult>; tsServerRequestDiagnostics(filePaths: string[]): Promise<void>; tsServerGetRenameLocations(filePath: string, line: number, offset: number): Promise<import("./tsserver-manager").TsServerRequestResult>; tsServerGetNavTree(filePath: string): Promise<import("./tsserver-manager").TsServerRequestResult>; tsServerOrganizeImports(filePath: string): Promise<import("./tsserver-manager").TsServerRequestResult>; tsServerGetInlayHints(filePath: string): Promise<import("./tsserver-manager").TsServerRequestResult>; tsServerGetCodeFixes(filePath: string, startLine: number, startOffset: number, endLine: number, endOffset: number, errorCodes: number[]): Promise<import("./tsserver-manager").TsServerRequestResult>; tsServerGetApplicableRefactors(filePath: string, startLine: number, startOffset: number, endLine: number, endOffset: number): Promise<import("./tsserver-manager").TsServerRequestResult>; tsServerGetEditsForRefactor(filePath: string, startLine: number, startOffset: number, endLine: number, endOffset: number, refactorName: string, actionName: string): Promise<import("./tsserver-manager").TsServerRequestResult>; tsServerGetImplementation(filePath: string, line: number, offset: number): Promise<import("./tsserver-manager").TsServerRequestResult>; tsServerGetSelectionRange(filePath: string, locations: Array<{ line: number; offset: number; }>): Promise<import("./tsserver-manager").TsServerRequestResult>; tsServerGetLinkedEditingRange(filePath: string, line: number, offset: number): Promise<import("./tsserver-manager").TsServerRequestResult>; tsServerGetFormatRange(filePath: string, startLine: number, startOffset: number, endLine: number, endOffset: number): Promise<import("./tsserver-manager").TsServerRequestResult>; subscribeToDiagnostics(onEvent: (event: any) => void): () => void; close(opts?: { reason?: string; uploadGitBackup?: boolean; }): Promise<void>; sendMessageToOrgAgent(options: SendMessageToOrgAgentInput): Promise<{ status: "success"; } | { status: "error"; error: string; }>; /** * Queues a semantic git status reminder for the LLM. * Call this after significant git operations to keep the LLM informed. */ queueGitStatusReminder(context: { trigger: "session-start" | "sync-success" | "push-success" | "pull-success"; syncedBranches?: string[]; pushedToRemote?: boolean; }): void; manualCommit(options: { add: string; commitMessage: string; folderName?: string; }): Promise<boolean>; getLastSuggestedAction(): SuggestedActionBuildError | undefined; connectToEventLoop(shouldReplay: boolean, onStep: (step: GenerateCompletionStep) => void, options?: { canHandleTools?: (keyof CodeGenToolMap)[]; }): () => void; waitUntilPendingPromises(signal?: AbortSignal): Promise<void>; waitUntilBlockingPendingPromises(signal?: AbortSignal): Promise<void>; waitUntilIdle(signal?: AbortSignal): Promise<void>; waitForEventLoop(): Promise<void>; /** * Stages and optionally commits work in progress. * * When `#batchCommitsEnabled` is false (default): stages AND commits immediately. * When `#batchCommitsEnabled` is true: only stages files, sets #pendingCommit flag. * * @returns commit hash if committed, false if no changes or staged only */ commitWorkInProgress(defaultCommitMessage: string, changedFiles: string[]): Promise<void>; getChangesReport(): Promise<{ diff: string; files: string[]; } | undefined>; /** * Resolves a workspace file path to its actual file system path * @param filePath A file path that may include a workspace prefix (e.g., "workspace1/path/to/file.js") * @param forceWorkspace If true, will try the first workspace as fallback when no workspace folder is found * @returns The actual file system path and the workspace folder it belongs to */ resolveWorkspacePath(filePath: string, forceWorkspace: boolean): { resolvedPath: string; workspaceFolder?: WorkspaceFolder; }; /** * Converts an absolute file system path to a workspace URL * @param absolutePath The absolute file system path to convert * @returns The workspace URL if the path can be converted, undefined otherwise */ absolutePathToWorkspaceUrl(absolutePath: string): string | undefined; /** * Launches the editor for a given file path * @param filePath The file path to launch the editor for * @param line The line number to launch the editor at * @param column The column number to launch the editor at * @returns void */ launchEditor(opts?: { filePath?: string; line?: number; column?: number; }): Promise<{ success: boolean; message?: string; }>; /** * Reads a file from the workspace * @param filePath A file path that may include a workspace prefix * @returns The file content or null if the file doesn't exist */ readFile(filePath: string, skipAclCheck?: boolean): Promise<string | null>; /** * Reads a file and returns content with a checksum for caching. * If previousChecksum is provided and matches, returns notModified: true * without sending the file content. */ readFileWithChecksum(args: { filePath: string; previousChecksum?: string; skipAclCheck?: boolean; }): Promise<{ content: string | null; checksum: string | null; notModified?: boolean; }>; /** * Reads a binary file from the workspace and returns its content as base64. * Unlike readFile which reads as UTF-8 (corrupting binary data), this method * preserves binary content by encoding it as base64. */ readBinaryFile(filePath: string): Promise<string | null>; /** * Gets file content at a specific git reference (e.g., origin/main, HEAD~1, commit hash) * @param filePath - The file path relative to the workspace * @param gitRef - The git reference (branch, commit, tag) - defaults to parent branch * @returns The file content at that reference, or null if not found */ getFileAtRef(filePath: string, gitRef?: string, repoPathOverride?: string): Promise<string | null>; /** * Gets the diff information for a single file including full content * @param args - Either an options object or a file path string (legacy) * @param opts.path - The file path, can be folder-prefixed (e.g., 'builder-anime-works/client/App.tsx') or relative * @param opts.folderName - Optional folder name (deprecated, prefer folder-prefixed path) * @param opts.mode - Diff baseline mode. "remote-parent-branch" (default) * compares against the parent branch merge-base. "remote-current-branch" * compares against origin/<currentBranch> (with fallback to merge-base * when the branch has never been pushed). * @returns Object with oldContent (from baseline ref) and newContent (current) */ getSingleFileDiff(args: { path: string; folderName?: string; previousChecksum?: string; mode?: "remote-parent-branch" | "remote-current-branch"; } | string): Promise<{ oldContent: string | null; newContent: string | null; action: "create" | "update" | "delete"; checksum: string | null; notModified?: boolean; }>; /** * Discards changes for a specific file by creating a revert commit * @param args - Either an options object or a file path string (legacy) * @param opts.filePath - The file path, can be folder-prefixed (e.g., 'builder-anime-works/client/App.tsx') * @param opts.folderName - Optional folder name (deprecated, prefer folder-prefixed path) * @returns success status, commit hash for reverting, and optional error message */ discardFileChanges(args: { filePath: string; folderName?: string; } | string): Promise<{ success: boolean; commitHash?: string; error?: string; }>; /** * Reverts a discard commit using git revert * @param commitHash - The commit hash to revert * @param folderName - Optional folder name to target specific repo (more efficient) * @returns success status and optional error message */ revertDiscard(options: { commitHash: string; folderName?: string; }): Promise<{ success: boolean; error?: string; }>; /** * Checks if a file exists in the workspace * @param filePath A file path that may include a workspace prefix * @returns True if the file exists, false otherwise */ fileExists(filePath: string): Promise<{ absolutePath: string | undefined; recommendedPath: string | undefined; workspaceFolder: WorkspaceFolder | undefined; virtual: boolean; }>; /** * Reads a file from the workspace synchronously * @param filePath A file path that may include a workspace prefix * @returns The file content or null if the file doesn't exist */ readFileSync(filePath: string, skipAclCheck?: boolean): string | null; /** * Writes content to a file in the workspace * @param filePath A file path that may include a workspace prefix * @param content The content to write * @returns True if the write was successful, false otherwise */ writeFile(filePath: string, content: string | Uint8Array, skipAclCheck?: boolean): Promise<string | null>; /** * Lists files in a directory in the workspace * @param dirPath A directory path that may include a workspace prefix * @returns Array of file names in the directory or empty array if directory doesn't exist */ listDir(dirPath: string): Promise<string[]>; /** * List custom instructions (rules and skills) discovered from the workspace. * Returns instructions with isSkill flag to distinguish skills from rules. */ listCustomInstructions(): Promise<Array<{ id: string; name: string; description?: string; type?: string; isSkill: boolean; disableModelInvocation?: boolean; userInvocable?: boolean; }>>; /** * Get stats for a file in the workspace * @param filePath A file path that may include a workspace prefix * @returns The file stats or null if the file doesn't exist */ stat(filePath: string, skipAclCheck?: boolean): Promise<{ isDirectory: () => boolean; isFile: () => boolean; size: number; } | null>; /** * Deletes a file or directory from the workspace. * Directories are removed recursively along with all of their contents. * @param filePath A file or directory path that may include a workspace prefix * @returns The resolved path that was deleted, or null on failure / if it didn't exist */ deleteFile(filePath: string, skipAclCheck?: boolean): Promise<string | null>; getLinesStats(): { added: number; removed: number; net: number; }; /** * Get git diff between current commit and remote branch * If remote current branch doesn't exist, gets diff between default branch and current branch * @param numberOfContextLines - Optional number of context lines to include in the diff * @param includeFilesOnly - If true, only return filePath and action properties * @param filePaths - Optional array of file paths to limit the diff to specific files * @param folderName - Optional folder name to specify which repo to get diff from */ getDiffFromRemote({ numberOfContextLines, includeFilesOnly, filePaths, folderName }: { numberOfContextLines?: number; includeFilesOnly?: boolean; filePaths?: Array<string>; folderName?: string; }): Promise<ApplyActionsResult[]>; /** * Get git diff based on the specified mode * @param mode - The diff mode: 'previous-commit', 'parent-branch', or 'remote' * @param numberOfContextLines - Optional number of context lines to include in the diff (e.g., 999 for -U999) * @param includeFilesOnly - If true, only return filePath and action properties * @param filePaths - Optional array of file paths to limit the diff to specific files * @param folderName - Optional folder name to specify which repo to get diff from */ getDiff({ mode, numberOfContextLines, includeFilesOnly, filePaths, folderName }: { mode: "remote-parent-branch" | "remote-current-branch"; numberOfContextLines?: number; includeFilesOnly?: boolean; filePaths?: Array<string>; folderName?: string; }): Promise<{ state: "error" | "success"; diff?: ApplyActionsResult[]; error?: Error; }>; private getDefaultBranch; private getDiffFromParentBranch; }