{"version":3,"file":"mcp.mjs","names":["toolRead"],"sources":["../../src/schemas/readTool.ts","../../src/services/readImage/index.ts","../../src/services/readTool/index.ts","../../src/extensions/workspaces/sessions/(backend)/tool/read.mcp.ts","../../generated/mcp.ts"],"sourcesContent":["import { type Static, Type } from 'typebox';\n\nexport const ReadParamsSchema = Type.Object({\n  path: Type.String({ description: 'Path to the file to read.' }),\n  offset: Type.Optional(Type.Integer({ minimum: 1, description: 'One-based line offset.' })),\n  limit: Type.Optional(Type.Integer({ minimum: 1, description: 'Maximum number of lines to return.' })),\n});\n\nexport type ReadParams = Static<typeof ReadParamsSchema>;\n","import { loadPiImageSettings, type PiImageSettings } from '@agimon-ai/doompi-config/pi-config';\n\nconst OMITTED_NOTE = '[Image omitted: could not be resized below the inline image size limit.]';\n\nexport interface ReadTextPart {\n  type: 'text';\n  text: string;\n}\n\nexport interface ReadImagePart {\n  type: 'image';\n  data: string;\n  mimeType: string;\n}\n\nexport type ReadContentPart = ReadTextPart | ReadImagePart;\n\nexport interface ReadImageResizeResult {\n  readonly data: string;\n  readonly mimeType: string;\n  readonly originalWidth: number;\n  readonly originalHeight: number;\n  readonly width: number;\n  readonly height: number;\n  readonly wasResized: boolean;\n}\n\nexport interface ReadImageResizeOperations {\n  resize(\n    inputBytes: Uint8Array,\n    mimeType: string,\n    options: { readonly maxWidth: number; readonly maxHeight: number },\n  ): Promise<ReadImageResizeResult | null>;\n  formatDimensionNote(result: ReadImageResizeResult): string | undefined;\n}\n\nexport function imageLimits(): PiImageSettings {\n  return loadPiImageSettings();\n}\n\nexport async function applyImageLimits(\n  content: ReadContentPart[],\n  limits: PiImageSettings,\n  operations: ReadImageResizeOperations,\n): Promise<ReadContentPart[]> {\n  if (!limits.autoResize || !content.some((part) => part.type === 'image')) return content;\n  const resolved: ReadContentPart[] = [];\n  for (const part of content) {\n    if (part.type !== 'image') {\n      resolved.push(part);\n      continue;\n    }\n    const resized = await operations.resize(Buffer.from(part.data, 'base64'), part.mimeType, {\n      maxWidth: limits.maxDimension,\n      maxHeight: limits.maxDimension,\n    });\n    if (!resized) {\n      resolved.push({ type: 'text', text: OMITTED_NOTE });\n      continue;\n    }\n    resolved.push({ type: 'image', data: resized.data, mimeType: resized.mimeType });\n    const note = operations.formatDimensionNote(resized);\n    if (note) resolved.push({ type: 'text', text: note });\n  }\n  return resolved;\n}\n","import { readFile } from 'node:fs/promises';\n\nimport type {\n  DoomHeadlessExecutionContext,\n  DoomHeadlessTool,\n  DoomHeadlessToolResult,\n} from '@agimon-ai/doompi-core/headless';\nimport { formatFileHeader, formatTaggedLine, splitLines } from '@agimon-ai/doompi-hashline';\nimport {\n  computeFileTag,\n  decodeUtf8,\n  displayPath,\n  isWritableFile,\n  resolveReadInputPath,\n} from '@agimon-ai/doompi-hashline/files';\nimport {\n  DEFAULT_MAX_BYTES,\n  formatDimensionNote,\n  formatSize,\n  resizeImage,\n  truncateHead,\n  truncateLine,\n  type ReadToolDetails,\n} from '@earendil-works/pi-coding-agent';\n\nimport { ReadParamsSchema, type ReadParams } from '../../schemas/readTool';\nimport {\n  applyImageLimits,\n  imageLimits,\n  type ReadContentPart,\n  type ReadImageResizeOperations,\n  type ReadTextPart,\n} from '../readImage';\n\nconst resizeOperations: ReadImageResizeOperations = {\n  resize: resizeImage,\n  formatDimensionNote: (result) => formatDimensionNote(result),\n};\n\nexport interface ReadToolResult {\n  content: ReadContentPart[];\n  details?: ReadToolDetails;\n}\n\ninterface ReadTextToolResult {\n  content: ReadTextPart[];\n  details?: ReadToolDetails;\n}\n\nexport function assertNotAborted(signal: AbortSignal | undefined): void {\n  if (signal?.aborted) throw new Error('Operation aborted');\n}\n\nexport function isImagePath(filePath: string): string | undefined {\n  const extension = filePath.toLowerCase().match(/\\.([a-z0-9]+)$/u)?.[1];\n  if (!extension) return undefined;\n  const mimeTypes: Record<string, string> = {\n    avif: 'image/avif',\n    gif: 'image/gif',\n    jpeg: 'image/jpeg',\n    jpg: 'image/jpeg',\n    png: 'image/png',\n    webp: 'image/webp',\n  };\n  return mimeTypes[extension];\n}\n\nexport async function executeHeadlessRead(\n  params: ReadParams,\n  cwd: string,\n  signal: AbortSignal | undefined,\n): Promise<ReadToolResult> {\n  assertNotAborted(signal);\n  const absolutePath = await resolveReadInputPath(params.path, cwd);\n  const bytes = await readFile(absolutePath);\n  assertNotAborted(signal);\n  const mimeType = isImagePath(absolutePath);\n  if (mimeType) {\n    const content: ReadContentPart[] = [\n      { type: 'text', text: `Read image file ${displayPath(absolutePath, cwd)} [${mimeType}]` },\n      { type: 'image', data: bytes.toString('base64'), mimeType },\n    ];\n    return { content: await applyImageLimits(content, imageLimits(), resizeOperations) };\n  }\n  if (!(await isWritableFile(absolutePath))) {\n    return { content: [{ type: 'text', text: decodeUtf8(bytes, displayPath(absolutePath, cwd)) }] };\n  }\n  return createTaggedReadResult(bytes, displayPath(absolutePath, cwd), params);\n}\n\nexport function createTaggedReadResult(bytes: Buffer, path: string, params: ReadParams): ReadTextToolResult {\n  const lines = splitLines(decodeUtf8(bytes, path));\n  const startIndex = params.offset === undefined ? 0 : params.offset - 1;\n  if (startIndex >= lines.length) {\n    throw new Error(`Offset ${params.offset} is beyond end of file (${lines.length} lines total).`);\n  }\n\n  const endIndex = params.limit === undefined ? lines.length : Math.min(lines.length, startIndex + params.limit);\n  const selected = lines.slice(startIndex, endIndex);\n  const header = formatFileHeader(path, computeFileTag(bytes));\n  const compactedLines: number[] = [];\n  const headerBytes = Buffer.byteLength(header, 'utf8') + 1;\n  const tagged = selected.map((line, index) => {\n    const lineNumber = startIndex + index + 1;\n    const full = formatTaggedLine(line, lineNumber);\n    if (Buffer.byteLength(full, 'utf8') <= DEFAULT_MAX_BYTES - headerBytes) return full;\n    compactedLines.push(lineNumber);\n    return formatTaggedLine(truncateLine(line).text, lineNumber, '', line);\n  });\n  const truncation = truncateHead([header, ...tagged].join('\\n'));\n  let text = truncation.content;\n  let details: ReadToolDetails | undefined;\n\n  if (truncation.truncated) {\n    const shownLines = Math.max(0, truncation.outputLines - 1);\n    const nextOffset = startIndex + shownLines + 1;\n    const reason = truncation.truncatedBy === 'bytes' ? `, ${formatSize(DEFAULT_MAX_BYTES)} limit` : '';\n    text += `\\n\\n[Showing ${shownLines} anchored lines${reason}. Use offset=${nextOffset} to continue.]`;\n    details = { truncation };\n  } else {\n    const notices: string[] = [];\n    if (compactedLines.length > 0) {\n      notices.push(`Lines ${compactedLines.join(', ')} shown compactly. Their anchors hash the full original lines`);\n    }\n    if (endIndex < lines.length) {\n      notices.push(`${lines.length - endIndex} more lines in file. Use offset=${endIndex + 1} to continue`);\n    }\n    if (notices.length > 0) text += `\\n\\n[${notices.join('. ')}.]`;\n  }\n\n  return { content: [{ type: 'text', text }], details };\n}\n\nexport function isImageRead(content: readonly { readonly type: string; readonly text?: string }[]): boolean {\n  return content.some((part) => part.type === 'image' || part.text?.startsWith('Read image file') === true);\n}\n\nexport function createHeadlessReadTool(): DoomHeadlessTool<typeof ReadParamsSchema> {\n  return {\n    name: 'read',\n    label: 'read',\n    description:\n      'Read a writable text file with an exact-byte file tag and stable line anchors. Non-writable files and images return native-compatible content. Text is truncated to 50.0KB.',\n    promptSnippet: 'Read file contents with snapshot-bound line anchors',\n    promptGuidelines: [\n      'Use read before edit. When hashline metadata is present, preserve the @file hash and anchors such as 5#abc exactly.',\n      'Continue large reads with offset until the required anchored lines are visible.',\n    ],\n    parameters: ReadParamsSchema,\n    executionMode: 'parallel',\n    execute: async (\n      _toolCallId: string,\n      params: ReadParams,\n      signal: AbortSignal | undefined,\n      _onUpdate: ((result: DoomHeadlessToolResult) => void) | undefined,\n      context: DoomHeadlessExecutionContext,\n    ) => executeHeadlessRead(params, context.cwd, signal),\n  };\n}\n","import { defineMcpTool } from '@agimon-ai/doompi-core/mcp-facet';\n\nimport { createHeadlessReadTool } from '../../../../../services/readTool';\n\nexport default defineMcpTool(createHeadlessReadTool);\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { defineMcpPlugin, type DoomMcpSessionPlugin } from '@agimon-ai/doompi-core/mcp-facet';\n\nimport toolRead from '../src/extensions/workspaces/sessions/(backend)/tool/read.mcp';\n\ntype Factory<T, C> = (context: C) => T;\nconst at = <T, C>(value: T | Factory<T, C>, context: C): T =>\n  typeof value === 'function' ? (value as Factory<T, C>)(context) : value;\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T => ({ ...identity, ...(value as object) }) as T;\n\nexport const mcp = defineMcpPlugin({\n  name: '@agimon-ai/doompi-read',\n  session: (context) => ({\n    get tools(): DoomMcpSessionPlugin['tools'] { return [via({ name: 'read' }, at(toolRead, context))]; },\n  }) satisfies DoomMcpSessionPlugin,\n});\n\nexport default mcp;\n"],"mappings":"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"}