Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/opencode/src/server/routes/instance/experimental.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Hono } from "hono"
import { describeRoute, validator, resolver } from "hono-openapi"
import z from "zod"
import * as EffectZod from "@/util/effect-zod"
import { ProviderID, ModelID } from "@/provider/schema"
import { ToolRegistry } from "@/tool"
import { Worktree } from "@/worktree"
Expand Down Expand Up @@ -213,7 +214,7 @@ export const ExperimentalRoutes = lazy(() =>
tools.map((t) => ({
id: t.id,
description: t.description,
parameters: z.toJSONSchema(t.parameters),
parameters: EffectZod.toJsonSchema(t.parameters),
})),
)
},
Expand Down
3 changes: 2 additions & 1 deletion packages/opencode/src/session/prompt.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import path from "path"
import os from "os"
import z from "zod"
import * as EffectZod from "@/util/effect-zod"
import { SessionID, MessageID, PartID } from "./schema"
import { MessageV2 } from "./message-v2"
import { Log } from "../util"
Expand Down Expand Up @@ -403,7 +404,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
providerID: input.model.providerID,
agent: input.agent,
})) {
const schema = ProviderTransform.schema(input.model, z.toJSONSchema(item.parameters))
const schema = ProviderTransform.schema(input.model, EffectZod.toJsonSchema(item.parameters))
tools[item.id] = tool({
description: item.description,
inputSchema: jsonSchema(schema),
Expand Down
13 changes: 6 additions & 7 deletions packages/opencode/src/tool/apply_patch.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import z from "zod"
import * as path from "path"
import { Effect } from "effect"
import { Effect, Schema } from "effect"
import * as Tool from "./tool"
import { Bus } from "../bus"
import { FileWatcher } from "../file/watcher"
Expand All @@ -15,8 +14,8 @@ import DESCRIPTION from "./apply_patch.txt"
import { File } from "../file"
import { Format } from "../format"

const PatchParams = z.object({
patchText: z.string().describe("The full patch text that describes all changes to be made"),
export const Parameters = Schema.Struct({
patchText: Schema.String.annotate({ description: "The full patch text that describes all changes to be made" }),
})

export const ApplyPatchTool = Tool.define(
Expand All @@ -27,7 +26,7 @@ export const ApplyPatchTool = Tool.define(
const format = yield* Format.Service
const bus = yield* Bus.Service

const run = Effect.fn("ApplyPatchTool.execute")(function* (params: z.infer<typeof PatchParams>, ctx: Tool.Context) {
const run = Effect.fn("ApplyPatchTool.execute")(function* (params: Schema.Schema.Type<typeof Parameters>, ctx: Tool.Context) {
if (!params.patchText) {
return yield* Effect.fail(new Error("patchText is required"))
}
Expand Down Expand Up @@ -287,8 +286,8 @@ export const ApplyPatchTool = Tool.define(

return {
description: DESCRIPTION,
parameters: PatchParams,
execute: (params: z.infer<typeof PatchParams>, ctx: Tool.Context) => run(params, ctx).pipe(Effect.orDie),
parameters: Parameters,
execute: (params: Schema.Schema.Type<typeof Parameters>, ctx: Tool.Context) => run(params, ctx).pipe(Effect.orDie),
}
}),
)
26 changes: 11 additions & 15 deletions packages/opencode/src/tool/bash.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import z from "zod"
import { Schema } from "effect"
import os from "os"
import { createWriteStream } from "node:fs"
import * as Tool from "./tool"
Expand Down Expand Up @@ -50,20 +50,16 @@ const FILES = new Set([
const FLAGS = new Set(["-destination", "-literalpath", "-path"])
const SWITCHES = new Set(["-confirm", "-debug", "-force", "-nonewline", "-recurse", "-verbose", "-whatif"])

const Parameters = z.object({
command: z.string().describe("The command to execute"),
timeout: z.number().describe("Optional timeout in milliseconds").optional(),
workdir: z
.string()
.describe(
`The working directory to run the command in. Defaults to the current directory. Use this instead of 'cd' commands.`,
)
.optional(),
description: z
.string()
.describe(
export const Parameters = Schema.Struct({
command: Schema.String.annotate({ description: "The command to execute" }),
timeout: Schema.optional(Schema.Number).annotate({ description: "Optional timeout in milliseconds" }),
workdir: Schema.optional(Schema.String).annotate({
description: `The working directory to run the command in. Defaults to the current directory. Use this instead of 'cd' commands.`,
}),
description: Schema.String.annotate({
description:
"Clear, concise description of what this command does in 5-10 words. Examples:\nInput: ls\nOutput: Lists files in current directory\n\nInput: git status\nOutput: Shows working tree status\n\nInput: npm install\nOutput: Installs package dependencies\n\nInput: mkdir foo\nOutput: Creates directory 'foo'",
),
}),
})

type Part = {
Expand Down Expand Up @@ -587,7 +583,7 @@ export const BashTool = Tool.define(
.replaceAll("${maxLines}", String(Truncate.MAX_LINES))
.replaceAll("${maxBytes}", String(Truncate.MAX_BYTES)),
parameters: Parameters,
execute: (params: z.infer<typeof Parameters>, ctx: Tool.Context) =>
execute: (params: Schema.Schema.Type<typeof Parameters>, ctx: Tool.Context) =>
Effect.gen(function* () {
const cwd = params.workdir
? yield* resolvePath(params.workdir, Instance.directory, shell)
Expand Down
35 changes: 17 additions & 18 deletions packages/opencode/src/tool/codesearch.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,31 @@
import z from "zod"
import { Effect } from "effect"
import { Effect, Schema } from "effect"
import { HttpClient } from "effect/unstable/http"
import * as Tool from "./tool"
import * as McpExa from "./mcp-exa"
import DESCRIPTION from "./codesearch.txt"

export const Parameters = Schema.Struct({
query: Schema.String.annotate({
description:
"Search query to find relevant context for APIs, Libraries, and SDKs. For example, 'React useState hook examples', 'Python pandas dataframe filtering', 'Express.js middleware', 'Next js partial prerendering configuration'",
}),
tokensNum: Schema.Number.check(Schema.isGreaterThanOrEqualTo(1000))
.check(Schema.isLessThanOrEqualTo(50000))
.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed(5000)))
.annotate({
description:
"Number of tokens to return (1000-50000). Default is 5000 tokens. Adjust this value based on how much context you need - use lower values for focused queries and higher values for comprehensive documentation.",
}),
})

export const CodeSearchTool = Tool.define(
"codesearch",
Effect.gen(function* () {
const http = yield* HttpClient.HttpClient

return {
description: DESCRIPTION,
parameters: z.object({
query: z
.string()
.describe(
"Search query to find relevant context for APIs, Libraries, and SDKs. For example, 'React useState hook examples', 'Python pandas dataframe filtering', 'Express.js middleware', 'Next js partial prerendering configuration'",
),
tokensNum: z
.number()
.min(1000)
.max(50000)
.default(5000)
.describe(
"Number of tokens to return (1000-50000). Default is 5000 tokens. Adjust this value based on how much context you need - use lower values for focused queries and higher values for comprehensive documentation.",
),
}),
parameters: Parameters,
execute: (params: { query: string; tokensNum: number }, ctx: Tool.Context) =>
Effect.gen(function* () {
yield* ctx.ask({
Expand All @@ -45,7 +44,7 @@ export const CodeSearchTool = Tool.define(
McpExa.CodeArgs,
{
query: params.query,
tokensNum: params.tokensNum || 5000,
tokensNum: params.tokensNum,
},
"30 seconds",
)
Expand Down
19 changes: 11 additions & 8 deletions packages/opencode/src/tool/edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
// https://github.com/google-gemini/gemini-cli/blob/main/packages/core/src/utils/editCorrector.ts
// https://github.com/cline/cline/blob/main/evals/diff-edits/diff-apply/diff-06-26-25.ts

import z from "zod"
import * as path from "path"
import { Effect } from "effect"
import { Effect, Schema } from "effect"
import * as Tool from "./tool"
import { LSP } from "../lsp"
import { createTwoFilesPatch, diffLines } from "diff"
Expand All @@ -32,11 +31,15 @@ function convertToLineEnding(text: string, ending: "\n" | "\r\n"): string {
return text.replaceAll("\n", "\r\n")
}

const Parameters = z.object({
filePath: z.string().describe("The absolute path to the file to modify"),
oldString: z.string().describe("The text to replace"),
newString: z.string().describe("The text to replace it with (must be different from oldString)"),
replaceAll: z.boolean().optional().describe("Replace all occurrences of oldString (default false)"),
export const Parameters = Schema.Struct({
filePath: Schema.String.annotate({ description: "The absolute path to the file to modify" }),
oldString: Schema.String.annotate({ description: "The text to replace" }),
newString: Schema.String.annotate({
description: "The text to replace it with (must be different from oldString)",
}),
replaceAll: Schema.optional(Schema.Boolean).annotate({
description: "Replace all occurrences of oldString (default false)",
}),
})

export const EditTool = Tool.define(
Expand All @@ -50,7 +53,7 @@ export const EditTool = Tool.define(
return {
description: DESCRIPTION,
parameters: Parameters,
execute: (params: z.infer<typeof Parameters>, ctx: Tool.Context) =>
execute: (params: Schema.Schema.Type<typeof Parameters>, ctx: Tool.Context) =>
Effect.gen(function* () {
if (!params.filePath) {
throw new Error("filePath is required")
Expand Down
20 changes: 9 additions & 11 deletions packages/opencode/src/tool/glob.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import path from "path"
import z from "zod"
import { Effect, Option } from "effect"
import { Effect, Option, Schema } from "effect"
import * as Stream from "effect/Stream"
import { InstanceState } from "@/effect"
import { AppFileSystem } from "@opencode-ai/shared/filesystem"
Expand All @@ -9,6 +8,13 @@ import { assertExternalDirectoryEffect } from "./external-directory"
import DESCRIPTION from "./glob.txt"
import * as Tool from "./tool"

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.`,
}),
})

export const GlobTool = Tool.define(
"glob",
Effect.gen(function* () {
Expand All @@ -17,15 +23,7 @@ export const GlobTool = Tool.define(

return {
description: DESCRIPTION,
parameters: z.object({
pattern: z.string().describe("The glob pattern to match files against"),
path: z
.string()
.optional()
.describe(
`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.`,
),
}),
parameters: Parameters,
execute: (params: { pattern: string; path?: string }, ctx: Tool.Context) =>
Effect.gen(function* () {
const ins = yield* InstanceState.context
Expand Down
18 changes: 12 additions & 6 deletions packages/opencode/src/tool/grep.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import path from "path"
import z from "zod"
import { Schema } from "effect"
import { Effect, Option } from "effect"
import { InstanceState } from "@/effect"
import { AppFileSystem } from "@opencode-ai/shared/filesystem"
Expand All @@ -10,6 +10,16 @@ import * as Tool from "./tool"

const MAX_LINE_LENGTH = 2000

export const Parameters = Schema.Struct({
pattern: Schema.String.annotate({ description: "The regex pattern to search for in file contents" }),
path: Schema.optional(Schema.String).annotate({
description: "The directory to search in. Defaults to the current working directory.",
}),
include: Schema.optional(Schema.String).annotate({
description: 'File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}")',
}),
})

export const GrepTool = Tool.define(
"grep",
Effect.gen(function* () {
Expand All @@ -18,11 +28,7 @@ export const GrepTool = Tool.define(

return {
description: DESCRIPTION,
parameters: z.object({
pattern: z.string().describe("The regex pattern to search for in file contents"),
path: z.string().optional().describe("The directory to search in. Defaults to the current working directory."),
include: z.string().optional().describe('File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}")'),
}),
parameters: Parameters,
execute: (params: { pattern: string; path?: string; include?: string }, ctx: Tool.Context) =>
Effect.gen(function* () {
const empty = {
Expand Down
13 changes: 7 additions & 6 deletions packages/opencode/src/tool/invalid.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import z from "zod"
import { Effect } from "effect"
import { Effect, Schema } from "effect"
import * as Tool from "./tool"

export const Parameters = Schema.Struct({
tool: Schema.String,
error: Schema.String,
})

export const InvalidTool = Tool.define(
"invalid",
Effect.succeed({
description: "Do not use",
parameters: z.object({
tool: z.string(),
error: z.string(),
}),
parameters: Parameters,
execute: (params: { tool: string; error: string }) =>
Effect.succeed({
title: "Invalid Tool",
Expand Down
21 changes: 13 additions & 8 deletions packages/opencode/src/tool/lsp.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import z from "zod"
import { Effect } from "effect"
import { Effect, Schema } from "effect"
import * as Tool from "./tool"
import path from "path"
import { LSP } from "../lsp"
Expand All @@ -21,6 +20,17 @@ const operations = [
"outgoingCalls",
] as const

export const Parameters = Schema.Struct({
operation: Schema.Literals(operations).annotate({ description: "The LSP operation to perform" }),
filePath: Schema.String.annotate({ description: "The absolute or relative path to the file" }),
line: Schema.Number.check(Schema.isInt())
.check(Schema.isGreaterThanOrEqualTo(1))
.annotate({ description: "The line number (1-based, as shown in editors)" }),
character: Schema.Number.check(Schema.isInt())
.check(Schema.isGreaterThanOrEqualTo(1))
.annotate({ description: "The character offset (1-based, as shown in editors)" }),
})

export const LspTool = Tool.define(
"lsp",
Effect.gen(function* () {
Expand All @@ -29,12 +39,7 @@ export const LspTool = Tool.define(

return {
description: DESCRIPTION,
parameters: z.object({
operation: z.enum(operations).describe("The LSP operation to perform"),
filePath: z.string().describe("The absolute or relative path to the file"),
line: z.number().int().min(1).describe("The line number (1-based, as shown in editors)"),
character: z.number().int().min(1).describe("The character offset (1-based, as shown in editors)"),
}),
parameters: Parameters,
execute: (
args: { operation: (typeof operations)[number]; filePath: string; line: number; character: number },
ctx: Tool.Context,
Expand Down
Loading
Loading