2025-06-04 17:12:13 +00:00
import path from "path"
2026-04-23 20:09:34 +00:00
import { Effect , Option , Schema } from "effect"
2026-04-10 17:56:42 +00:00
import * as Stream from "effect/Stream"
2026-04-27 18:33:33 +00:00
import { InstanceState } from "@/effect/instance-state"
2026-04-25 14:59:17 +00:00
import { AppFileSystem } from "@opencode-ai/core/filesystem"
2025-06-30 18:46:18 +00:00
import { Ripgrep } from "../file/ripgrep"
2026-04-10 17:56:42 +00:00
import { assertExternalDirectoryEffect } from "./external-directory"
2026-04-14 06:26:23 +00:00
import DESCRIPTION from "./glob.txt"
2026-04-16 03:56:54 +00:00
import * as Tool from "./tool"
2026-05-10 12:27:11 +00:00
import { Reference } from "@/reference/reference"
2025-05-21 14:30:39 +00:00
2026-04-23 20:09:34 +00:00
export const Parameters = Schema . Struct ( {
pattern : Schema.String.annotate ( { description : "The glob pattern to match files against" } ) ,
path : Schema.optional ( Schema . String ) . annotate ( {
description : ` The directory to search in. If not specified, the current working directory will be used. IMPORTANT: Omit this field to use the default directory. DO NOT enter "undefined" or "null" - simply omit it for the default behavior. Must be a valid directory path if provided. ` ,
} ) ,
} )
2026-04-11 02:36:02 +00:00
export const GlobTool = Tool . define (
2026-04-10 17:56:42 +00:00
"glob" ,
Effect . gen ( function * ( ) {
const rg = yield * Ripgrep . Service
const fs = yield * AppFileSystem . Service
2026-05-10 12:27:11 +00:00
const reference = yield * Reference . Service
2026-01-01 22:54:11 +00:00
2026-04-10 17:56:42 +00:00
return {
description : DESCRIPTION ,
2026-04-23 20:09:34 +00:00
parameters : Parameters ,
2026-04-10 17:56:42 +00:00
execute : ( params : { pattern : string ; path? : string } , ctx : Tool.Context ) = >
Effect . gen ( function * ( ) {
2026-04-14 06:26:23 +00:00
const ins = yield * InstanceState . context
2026-04-11 02:36:02 +00:00
yield * ctx . ask ( {
permission : "glob" ,
patterns : [ params . pattern ] ,
always : [ "*" ] ,
metadata : {
pattern : params.pattern ,
path : params.path ,
} ,
} )
2025-06-04 17:12:13 +00:00
2026-04-14 06:26:23 +00:00
let search = params . path ? ? ins . directory
search = path . isAbsolute ( search ) ? search : path.resolve ( ins . directory , search )
2026-05-10 12:27:11 +00:00
yield * reference . ensure ( search )
2026-04-13 23:26:50 +00:00
const info = yield * fs . stat ( search ) . pipe ( Effect . catch ( ( ) = > Effect . succeed ( undefined ) ) )
if ( info ? . type === "File" ) {
throw new Error ( ` glob path must be a directory: ${ search } ` )
}
2026-05-10 12:27:11 +00:00
yield * assertExternalDirectoryEffect ( ctx , search , {
bypass : yield * reference . contains ( search ) ,
kind : "directory" ,
} )
2025-05-21 14:30:39 +00:00
2026-04-10 17:56:42 +00:00
const limit = 100
let truncated = false
2026-04-14 06:26:23 +00:00
const files = yield * rg . files ( { cwd : search , glob : [ params . pattern ] , signal : ctx.abort } ) . pipe (
2026-04-10 17:56:42 +00:00
Stream . mapEffect ( ( file ) = >
Effect . gen ( function * ( ) {
const full = path . resolve ( search , file )
const info = yield * fs . stat ( full ) . pipe ( Effect . catch ( ( ) = > Effect . succeed ( undefined ) ) )
2026-04-10 17:57:46 +00:00
const mtime =
info ? . mtime . pipe (
2026-04-14 06:26:23 +00:00
Option . map ( ( date ) = > date . getTime ( ) ) ,
2026-04-10 17:57:46 +00:00
Option . getOrElse ( ( ) = > 0 ) ,
) ? ? 0
2026-04-10 17:56:42 +00:00
return { path : full , mtime }
} ) ,
) ,
Stream . take ( limit + 1 ) ,
Stream . runCollect ,
Effect . map ( ( chunk ) = > [ . . . chunk ] ) ,
)
2025-05-21 14:30:39 +00:00
2026-04-10 17:56:42 +00:00
if ( files . length > limit ) {
truncated = true
files . length = limit
}
files . sort ( ( a , b ) = > b . mtime - a . mtime )
const output = [ ]
if ( files . length === 0 ) output . push ( "No files found" )
if ( files . length > 0 ) {
2026-04-14 06:26:23 +00:00
output . push ( . . . files . map ( ( file ) = > file . path ) )
2026-04-10 17:56:42 +00:00
if ( truncated ) {
output . push ( "" )
output . push (
` (Results are truncated: showing first ${ limit } results. Consider using a more specific path or pattern.) ` ,
)
}
}
return {
2026-04-14 06:26:23 +00:00
title : path.relative ( ins . worktree , search ) ,
2026-04-10 17:56:42 +00:00
metadata : {
count : files.length ,
truncated ,
} ,
output : output.join ( "\n" ) ,
}
2026-04-11 02:36:02 +00:00
} ) . pipe ( Effect . orDie ) ,
2025-05-31 18:41:00 +00:00
}
2026-04-10 17:56:42 +00:00
} ) ,
)