import * as Schema from "effect/Schema"; import { NonNegativeInt, PositiveInt, TrimmedNonEmptyString, TrimmedString, } from "./baseSchemas.ts"; const PROJECT_SEARCH_ENTRIES_MAX_LIMIT = 200; const PROJECT_SEARCH_CONTENTS_MAX_LIMIT = 500; const PROJECT_WRITE_FILE_PATH_MAX_LENGTH = 512; const PROJECT_READ_FILE_PATH_MAX_LENGTH = 512; export const ProjectEntryKind = Schema.Literals(["file", "directory"]); export type ProjectEntryKind = typeof ProjectEntryKind.Type; export const ProjectSearchEntriesInput = Schema.Struct({ cwd: TrimmedNonEmptyString, // An empty query is a bounded browse: the index returns frecency-ordered // entries, which the file picker uses for its initial results. query: TrimmedString.check(Schema.isMaxLength(256)), limit: PositiveInt.check(Schema.isLessThanOrEqualTo(PROJECT_SEARCH_ENTRIES_MAX_LIMIT)), kind: Schema.optional(ProjectEntryKind), imageOnly: Schema.optional(Schema.Boolean), }); export type ProjectSearchEntriesInput = typeof ProjectSearchEntriesInput.Type; export const ProjectEntry = Schema.Struct({ path: TrimmedNonEmptyString, kind: ProjectEntryKind, }); export type ProjectEntry = typeof ProjectEntry.Type; export const ProjectSearchEntriesResult = Schema.Struct({ entries: Schema.Array(ProjectEntry), truncated: Schema.Boolean, }); export type ProjectSearchEntriesResult = typeof ProjectSearchEntriesResult.Type; export const ProjectSearchContentsInput = Schema.Struct({ cwd: TrimmedNonEmptyString, // Whitespace is significant in content queries (" foo", regex trailing // spaces), so the query is deliberately not trimmed on the wire. query: Schema.String.check(Schema.isNonEmpty(), Schema.isMaxLength(256)), limit: PositiveInt.check(Schema.isLessThanOrEqualTo(PROJECT_SEARCH_CONTENTS_MAX_LIMIT)), caseSensitive: Schema.Boolean, wholeWord: Schema.Boolean, useRegex: Schema.Boolean, }); export type ProjectSearchContentsInput = typeof ProjectSearchContentsInput.Type; export const ProjectContentMatchRange = Schema.Struct({ start: NonNegativeInt, end: NonNegativeInt, }); export type ProjectContentMatchRange = typeof ProjectContentMatchRange.Type; export const ProjectContentMatch = Schema.Struct({ path: TrimmedNonEmptyString, lineNumber: PositiveInt, lineContent: Schema.String, matchRanges: Schema.Array(ProjectContentMatchRange), }); export type ProjectContentMatch = typeof ProjectContentMatch.Type; export const ProjectSearchContentsResult = Schema.Struct({ matches: Schema.Array(ProjectContentMatch), truncated: Schema.Boolean, regexFallbackError: Schema.optional(Schema.String), }); export type ProjectSearchContentsResult = typeof ProjectSearchContentsResult.Type; export const ProjectListEntriesInput = Schema.Struct({ cwd: TrimmedNonEmptyString, }); export type ProjectListEntriesInput = typeof ProjectListEntriesInput.Type; export const ProjectListEntriesResult = Schema.Struct({ entries: Schema.Array(ProjectEntry), truncated: Schema.Boolean, }); export type ProjectListEntriesResult = typeof ProjectListEntriesResult.Type; export const ProjectEntriesFailure = Schema.Literals([ "workspace_root_not_found", "workspace_root_create_failed", "workspace_root_stat_failed", "workspace_root_not_directory", "search_index_create_failed", "search_index_scan_timed_out", "search_index_search_failed", ]); export type ProjectEntriesFailure = typeof ProjectEntriesFailure.Type; type ProjectEntriesFailureContext = { readonly failure: ProjectEntriesFailure; readonly normalizedCwd?: string; readonly timeout?: string; readonly detail?: string; readonly cause?: unknown; }; function decodedProjectErrorMessage(props: object): string | undefined { if (!("message" in props)) return undefined; return typeof props.message === "string" ? props.message : undefined; } export class ProjectSearchEntriesError extends Schema.TaggedErrorClass()( "ProjectSearchEntriesError", { cwd: Schema.optional(TrimmedNonEmptyString), queryLength: Schema.optional(NonNegativeInt), limit: Schema.optional(PositiveInt), failure: Schema.optional(ProjectEntriesFailure), normalizedCwd: Schema.optional(TrimmedNonEmptyString), timeout: Schema.optional(TrimmedNonEmptyString), detail: Schema.optional(TrimmedNonEmptyString), message: TrimmedNonEmptyString, cause: Schema.optional(Schema.Defect()), }, ) { // The structured fields are optional on the wire so newer peers can decode legacy message-only // failures. New application code must provide them through this constructor. // @effect-diagnostics-next-line overriddenSchemaConstructor:off constructor( props: ProjectEntriesFailureContext & { readonly cwd: string; readonly queryLength: number; readonly limit: number; }, ) { super({ ...props, message: decodedProjectErrorMessage(props) ?? `Failed to search workspace entries in '${props.cwd}'.`, } as any); } } export class ProjectSearchContentsError extends Schema.TaggedErrorClass()( "ProjectSearchContentsError", { cwd: Schema.optional(TrimmedNonEmptyString), queryLength: Schema.optional(NonNegativeInt), limit: Schema.optional(PositiveInt), failure: Schema.optional(ProjectEntriesFailure), normalizedCwd: Schema.optional(TrimmedNonEmptyString), timeout: Schema.optional(TrimmedNonEmptyString), detail: Schema.optional(TrimmedNonEmptyString), message: TrimmedNonEmptyString, cause: Schema.optional(Schema.Defect()), }, ) { // @effect-diagnostics-next-line overriddenSchemaConstructor:off constructor( props: ProjectEntriesFailureContext & { readonly cwd: string; readonly queryLength: number; readonly limit: number; }, ) { super({ ...props, message: decodedProjectErrorMessage(props) ?? `Failed to search workspace contents in '${props.cwd}'.`, } as any); } } export class ProjectListEntriesError extends Schema.TaggedErrorClass()( "ProjectListEntriesError", { cwd: Schema.optional(TrimmedNonEmptyString), failure: Schema.optional(ProjectEntriesFailure), normalizedCwd: Schema.optional(TrimmedNonEmptyString), timeout: Schema.optional(TrimmedNonEmptyString), detail: Schema.optional(TrimmedNonEmptyString), message: TrimmedNonEmptyString, cause: Schema.optional(Schema.Defect()), }, ) { // @effect-diagnostics-next-line overriddenSchemaConstructor:off constructor(props: ProjectEntriesFailureContext & { readonly cwd: string }) { super({ ...props, message: decodedProjectErrorMessage(props) ?? `Failed to list workspace entries in '${props.cwd}'.`, } as any); } } export const ProjectReadFileInput = Schema.Struct({ cwd: TrimmedNonEmptyString, relativePath: TrimmedNonEmptyString.check(Schema.isMaxLength(PROJECT_READ_FILE_PATH_MAX_LENGTH)), }); export type ProjectReadFileInput = typeof ProjectReadFileInput.Type; export const ProjectReadFileResult = Schema.Struct({ relativePath: TrimmedNonEmptyString, contents: Schema.String, byteLength: NonNegativeInt, truncated: Schema.Boolean, }); export type ProjectReadFileResult = typeof ProjectReadFileResult.Type; export const ProjectFileFailure = Schema.Literals([ "workspace_path_outside_root", "resolved_path_outside_root", "path_not_file", "binary_file", "operation_failed", ]); export type ProjectFileFailure = typeof ProjectFileFailure.Type; export const ProjectFileOperation = Schema.Literals([ "realpath-workspace-root", "realpath-target", "open", "stat", "read", "close", "make-directory", "write-file", ]); export type ProjectFileOperation = typeof ProjectFileOperation.Type; type ProjectFileFailureContext = { readonly cwd: string; readonly relativePath: string; readonly failure: ProjectFileFailure; readonly resolvedPath?: string; readonly resolvedWorkspaceRoot?: string; readonly operation?: ProjectFileOperation; readonly operationPath?: string; readonly cause?: unknown; }; export class ProjectReadFileError extends Schema.TaggedErrorClass()( "ProjectReadFileError", { cwd: Schema.optional(TrimmedNonEmptyString), relativePath: Schema.optional(TrimmedNonEmptyString), failure: Schema.optional(ProjectFileFailure), resolvedPath: Schema.optional(TrimmedNonEmptyString), resolvedWorkspaceRoot: Schema.optional(TrimmedNonEmptyString), operation: Schema.optional(ProjectFileOperation), operationPath: Schema.optional(TrimmedNonEmptyString), message: TrimmedNonEmptyString, cause: Schema.optional(Schema.Defect()), }, ) { // @effect-diagnostics-next-line overriddenSchemaConstructor:off constructor(props: ProjectFileFailureContext) { super({ ...props, message: decodedProjectErrorMessage(props) ?? `Failed to read workspace file '${props.relativePath}' in '${props.cwd}'.`, } as any); } } export const ProjectWriteFileInput = Schema.Struct({ cwd: TrimmedNonEmptyString, relativePath: TrimmedNonEmptyString.check(Schema.isMaxLength(PROJECT_WRITE_FILE_PATH_MAX_LENGTH)), contents: Schema.String, }); export type ProjectWriteFileInput = typeof ProjectWriteFileInput.Type; export const ProjectWriteFileResult = Schema.Struct({ relativePath: TrimmedNonEmptyString, }); export type ProjectWriteFileResult = typeof ProjectWriteFileResult.Type; export class ProjectWriteFileError extends Schema.TaggedErrorClass()( "ProjectWriteFileError", { cwd: Schema.optional(TrimmedNonEmptyString), relativePath: Schema.optional(TrimmedNonEmptyString), failure: Schema.optional(ProjectFileFailure), resolvedPath: Schema.optional(TrimmedNonEmptyString), resolvedWorkspaceRoot: Schema.optional(TrimmedNonEmptyString), operation: Schema.optional(ProjectFileOperation), operationPath: Schema.optional(TrimmedNonEmptyString), message: TrimmedNonEmptyString, cause: Schema.optional(Schema.Defect()), }, ) { // @effect-diagnostics-next-line overriddenSchemaConstructor:off constructor(props: ProjectFileFailureContext) { super({ ...props, message: decodedProjectErrorMessage(props) ?? `Failed to write workspace file '${props.relativePath}' in '${props.cwd}'.`, } as any); } }