fix(scout): use effect schema tool params

This commit is contained in:
Shoubhit Dash 2026-04-24 18:58:28 +05:30
commit c750df3e86
2 changed files with 32 additions and 42 deletions

View file

@ -1,6 +1,5 @@
import path from "path" import path from "path"
import z from "zod" import { Effect, Schema } from "effect"
import { Effect } from "effect"
import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { AppFileSystem } from "@opencode-ai/shared/filesystem"
import { Flock } from "@opencode-ai/shared/util/flock" import { Flock } from "@opencode-ai/shared/util/flock"
import { Git } from "@/git" import { Git } from "@/git"
@ -8,16 +7,13 @@ import DESCRIPTION from "./repo_clone.txt"
import * as Tool from "./tool" import * as Tool from "./tool"
import { parseRepositoryReference, repositoryCachePath, sameRepositoryReference } from "@/util/repository" import { parseRepositoryReference, repositoryCachePath, sameRepositoryReference } from "@/util/repository"
type Parameters = { export const Parameters = Schema.Struct({
repository: string repository: Schema.String.annotate({
refresh?: boolean description: "Repository to clone, as a git URL, host/path reference, or GitHub owner/repo shorthand",
} }),
refresh: Schema.optional(Schema.Boolean).annotate({
const parameters: z.ZodType<Parameters> = z.object({ description: "When true, fetches the latest remote state into the managed cache",
repository: z }),
.string()
.describe("Repository to clone, as a git URL, host/path reference, or GitHub owner/repo shorthand"),
refresh: z.boolean().optional().describe("When true, fetches the latest remote state into the managed cache"),
}) })
type Metadata = { type Metadata = {
@ -49,7 +45,7 @@ function resetTarget(input: {
return "HEAD" return "HEAD"
} }
export const RepoCloneTool = Tool.define<typeof parameters, Metadata, AppFileSystem.Service | Git.Service>( export const RepoCloneTool = Tool.define<typeof Parameters, Metadata, AppFileSystem.Service | Git.Service>(
"repo_clone", "repo_clone",
Effect.gen(function* () { Effect.gen(function* () {
const fs = yield* AppFileSystem.Service const fs = yield* AppFileSystem.Service
@ -57,8 +53,8 @@ export const RepoCloneTool = Tool.define<typeof parameters, Metadata, AppFileSys
return { return {
description: DESCRIPTION, description: DESCRIPTION,
parameters, parameters: Parameters,
execute: (params: Parameters, ctx: Tool.Context<Metadata>) => execute: (params: Schema.Schema.Type<typeof Parameters>, ctx: Tool.Context<Metadata>) =>
Effect.gen(function* () { Effect.gen(function* () {
const reference = parseRepositoryReference(params.repository) const reference = parseRepositoryReference(params.repository)
if (!reference) throw new Error("Repository must be a git URL, host/path reference, or GitHub owner/repo shorthand") if (!reference) throw new Error("Repository must be a git URL, host/path reference, or GitHub owner/repo shorthand")
@ -152,6 +148,6 @@ export const RepoCloneTool = Tool.define<typeof parameters, Metadata, AppFileSys
(lock) => Effect.promise(() => lock.release()).pipe(Effect.ignore), (lock) => Effect.promise(() => lock.release()).pipe(Effect.ignore),
) )
}).pipe(Effect.orDie), }).pipe(Effect.orDie),
} satisfies Tool.DefWithoutID<typeof parameters, Metadata> } satisfies Tool.DefWithoutID<typeof Parameters, Metadata>
}), }),
) )

View file

@ -1,6 +1,5 @@
import path from "path" import path from "path"
import z from "zod" import { Effect, Schema } from "effect"
import { Effect } from "effect"
import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { AppFileSystem } from "@opencode-ai/shared/filesystem"
import { Git } from "@/git" import { Git } from "@/git"
import { assertExternalDirectoryEffect } from "./external-directory" import { assertExternalDirectoryEffect } from "./external-directory"
@ -9,24 +8,17 @@ import * as Tool from "./tool"
import { parseRepositoryReference, repositoryCachePath } from "@/util/repository" import { parseRepositoryReference, repositoryCachePath } from "@/util/repository"
import { Instance } from "@/project/instance" import { Instance } from "@/project/instance"
type Parameters = { export const Parameters = Schema.Struct({
repository?: string repository: Schema.optional(Schema.String).annotate({
path?: string description: "Cached repository to inspect, as a git URL, host/path reference, or GitHub owner/repo shorthand",
depth?: number }),
} path: Schema.optional(Schema.String).annotate({
description: "Directory path to inspect instead of a cached repository",
const parameters: z.ZodType<Parameters> = z }),
.object({ depth: Schema.optional(Schema.Number).annotate({
repository: z description: "Maximum structure depth to include. Defaults to 3.",
.string()
.optional()
.describe("Cached repository to inspect, as a git URL, host/path reference, or GitHub owner/repo shorthand"),
path: z.string().optional().describe("Directory path to inspect instead of a cached repository"),
depth: z.number().int().positive().max(6).optional().describe("Maximum structure depth to include. Defaults to 3."),
})
.refine((input) => Boolean(input.repository || input.path), {
message: "Either repository or path is required",
}) })
})
type Metadata = { type Metadata = {
path: string path: string
@ -84,19 +76,21 @@ function commonEntrypoints(files: Set<string>) {
return ["index.ts", "index.tsx", "index.js", "index.mjs", "main.ts", "main.js", "src/index.ts", "src/index.tsx", "src/index.js", "src/main.ts", "src/main.js"].filter((file) => files.has(file)) return ["index.ts", "index.tsx", "index.js", "index.mjs", "main.ts", "main.js", "src/index.ts", "src/index.tsx", "src/index.js", "src/main.ts", "src/main.js"].filter((file) => files.has(file))
} }
export const RepoOverviewTool = Tool.define<typeof parameters, Metadata, AppFileSystem.Service | Git.Service>( export const RepoOverviewTool = Tool.define<typeof Parameters, Metadata, AppFileSystem.Service | Git.Service>(
"repo_overview", "repo_overview",
Effect.gen(function* () { Effect.gen(function* () {
const fs = yield* AppFileSystem.Service const fs = yield* AppFileSystem.Service
const git = yield* Git.Service const git = yield* Git.Service
const resolveTarget = Effect.fn("RepoOverviewTool.resolveTarget")(function* (params: Parameters) { const resolveTarget = Effect.fn("RepoOverviewTool.resolveTarget")(function* (params: Schema.Schema.Type<typeof Parameters>) {
if (params.path) { if (params.path) {
const full = path.isAbsolute(params.path) ? params.path : path.resolve(Instance.directory, params.path) const full = path.isAbsolute(params.path) ? params.path : path.resolve(Instance.directory, params.path)
return { path: full, repository: params.repository } return { path: full, repository: params.repository }
} }
const parsed = parseRepositoryReference(params.repository!) if (!params.repository) throw new Error("Either repository or path is required")
const parsed = parseRepositoryReference(params.repository)
if (!parsed) throw new Error("Repository must be a git URL, host/path reference, or GitHub owner/repo shorthand") if (!parsed) throw new Error("Repository must be a git URL, host/path reference, or GitHub owner/repo shorthand")
const repository = parsed.label const repository = parsed.label
@ -152,11 +146,11 @@ export const RepoOverviewTool = Tool.define<typeof parameters, Metadata, AppFile
return { return {
description: DESCRIPTION, description: DESCRIPTION,
parameters, parameters: Parameters,
execute: (params: Parameters, ctx: Tool.Context<Metadata>) => execute: (params: Schema.Schema.Type<typeof Parameters>, ctx: Tool.Context<Metadata>) =>
Effect.gen(function* () { Effect.gen(function* () {
const target = yield* resolveTarget(params) const target = yield* resolveTarget(params)
const depth = params.depth ?? 3 const depth = !params.depth || !Number.isInteger(params.depth) || params.depth < 1 || params.depth > 6 ? 3 : params.depth
yield* assertExternalDirectoryEffect(ctx, target.path, { kind: "directory" }) yield* assertExternalDirectoryEffect(ctx, target.path, { kind: "directory" })
yield* ctx.ask({ yield* ctx.ask({
@ -239,6 +233,6 @@ export const RepoOverviewTool = Tool.define<typeof parameters, Metadata, AppFile
].join("\n"), ].join("\n"),
} }
}).pipe(Effect.orDie), }).pipe(Effect.orDie),
} satisfies Tool.DefWithoutID<typeof parameters, Metadata> } satisfies Tool.DefWithoutID<typeof Parameters, Metadata>
}), }),
) )