mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-06 08:21:50 +00:00
refactor: make shell the canonical tool internals
This commit is contained in:
parent
b75f831eaa
commit
3e30068907
16 changed files with 36 additions and 60 deletions
|
|
@ -12,8 +12,6 @@ import matter from "gray-matter"
|
|||
import { Instance } from "../../project/instance"
|
||||
import { EOL } from "os"
|
||||
import type { Argv } from "yargs"
|
||||
import { ShellToolID } from "../../tool/shell/id"
|
||||
|
||||
type AgentMode = "all" | "primary" | "subagent"
|
||||
|
||||
const AVAILABLE_TOOLS = ["shell", "read", "write", "edit", "glob", "grep", "webfetch", "task", "todowrite"]
|
||||
|
|
@ -129,7 +127,7 @@ const AgentCreateCommand = cmd({
|
|||
...new Set(
|
||||
cliTools
|
||||
.split(",")
|
||||
.map((t) => ShellToolID.normalize(t.trim()))
|
||||
.map((t) => t.trim())
|
||||
.filter(Boolean),
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -880,7 +880,6 @@ export const GithubRunCommand = cmd({
|
|||
const TOOL: Record<string, [string, string]> = {
|
||||
todowrite: ["Todo", UI.Style.TEXT_WARNING_BOLD],
|
||||
shell: ["Shell", UI.Style.TEXT_DANGER_BOLD],
|
||||
bash: ["Shell", UI.Style.TEXT_DANGER_BOLD],
|
||||
edit: ["Edit", UI.Style.TEXT_SUCCESS_BOLD],
|
||||
glob: ["Glob", UI.Style.TEXT_INFO_BOLD],
|
||||
grep: ["Grep", UI.Style.TEXT_INFO_BOLD],
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import { CodeSearchTool } from "../../tool/codesearch"
|
|||
import { WebSearchTool } from "../../tool/websearch"
|
||||
import { TaskTool } from "../../tool/task"
|
||||
import { SkillTool } from "../../tool/skill"
|
||||
import { ShellTool } from "../../tool/shell/tool"
|
||||
import { ShellTool } from "../../tool/shell"
|
||||
import { ShellToolID } from "../../tool/shell/id"
|
||||
import { TodoWriteTool } from "../../tool/todo"
|
||||
import { Locale } from "../../util"
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ import { Locale } from "@/util"
|
|||
import type { Tool } from "@/tool"
|
||||
import type { ReadTool } from "@/tool/read"
|
||||
import type { WriteTool } from "@/tool/write"
|
||||
import { ShellTool } from "@/tool/shell/tool"
|
||||
import { ShellTool } from "@/tool/shell"
|
||||
import { ShellToolID } from "@/tool/shell/id"
|
||||
import type { GlobTool } from "@/tool/glob"
|
||||
import { TodoWriteTool } from "@/tool/todo"
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import { Auth } from "@/auth"
|
|||
import { Installation } from "@/installation"
|
||||
import { InstallationVersion } from "@/installation/version"
|
||||
import { EffectBridge } from "@/effect"
|
||||
import { ShellToolID } from "@/tool/shell/id"
|
||||
import * as Option from "effect/Option"
|
||||
import * as OtelTracer from "@effect/opentelemetry/Tracer"
|
||||
|
||||
|
|
@ -208,7 +207,7 @@ const live: Layer.Layer<
|
|||
input.model.api.id.toLowerCase().includes("litellm")
|
||||
|
||||
const repair = (toolName: string) => {
|
||||
const next = ShellToolID.normalize(toolName.toLowerCase())
|
||||
const next = toolName.toLowerCase()
|
||||
if (!tools[next]) return
|
||||
return next
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import type { SystemError } from "bun"
|
|||
import type { Provider } from "@/provider"
|
||||
import { ModelID, ProviderID } from "@/provider/schema"
|
||||
import { Effect, Schema, Types } from "effect"
|
||||
import { ShellToolID } from "@/tool/shell/id"
|
||||
import { zod, ZodOverride } from "@/util/effect-zod"
|
||||
import { withStatics } from "@/util/schema"
|
||||
import { namedSchemaError } from "@/util/named-schema-error"
|
||||
|
|
@ -443,17 +442,6 @@ export type Part =
|
|||
| RetryPart
|
||||
| CompactionPart
|
||||
|
||||
function normalizeTool(tool: string) {
|
||||
return ShellToolID.normalize(tool)
|
||||
}
|
||||
|
||||
function normalizePart<T extends Part>(part: T): T {
|
||||
if (part.type !== "tool") return part
|
||||
const tool = normalizeTool(part.tool)
|
||||
if (tool === part.tool) return part
|
||||
return { ...part, tool } as T
|
||||
}
|
||||
|
||||
// Errors are still NamedError-based Zod; bridge via ZodOverride so the derived
|
||||
// Zod + JSON Schema emit the original discriminatedUnion shape. Migrating the
|
||||
// error classes to Schema.TaggedErrorClass is a separate slice.
|
||||
|
|
@ -673,12 +661,12 @@ const info = (row: typeof MessageTable.$inferSelect) =>
|
|||
}) as Info
|
||||
|
||||
const part = (row: typeof PartTable.$inferSelect) =>
|
||||
normalizePart(({
|
||||
({
|
||||
...row.data,
|
||||
id: row.id,
|
||||
sessionID: row.session_id,
|
||||
messageID: row.message_id,
|
||||
} as Part))
|
||||
} as Part)
|
||||
|
||||
const older = (row: Cursor) =>
|
||||
or(lt(MessageTable.time_created, row.time), and(eq(MessageTable.time_created, row.time), lt(MessageTable.id, row.id)))
|
||||
|
|
@ -843,8 +831,7 @@ export const toModelMessagesEffect = Effect.fnUntraced(function* (
|
|||
role: "assistant",
|
||||
parts: [],
|
||||
}
|
||||
for (const raw of msg.parts) {
|
||||
const part = normalizePart(raw)
|
||||
for (const part of msg.parts) {
|
||||
if (part.type === "text")
|
||||
assistantMessage.parts.push({
|
||||
type: "text",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { PlanExitTool } from "./plan"
|
||||
import { Session } from "../session"
|
||||
import { QuestionTool } from "./question"
|
||||
import { ShellTool } from "./shell/tool"
|
||||
import { ShellTool } from "./shell"
|
||||
import { EditTool } from "./edit"
|
||||
import { GlobTool } from "./glob"
|
||||
import { GrepTool } from "./grep"
|
||||
|
|
|
|||
|
|
@ -253,13 +253,13 @@ function tail(text: string, maxLines: number, maxBytes: number) {
|
|||
}
|
||||
}
|
||||
|
||||
const parse = Effect.fn("BashTool.parse")(function* (command: string, ps: boolean) {
|
||||
const parse = Effect.fn("ShellTool.parse")(function* (command: string, ps: boolean) {
|
||||
const tree = yield* Effect.promise(() => parser().then((p) => (ps ? p.ps : p.bash).parse(command)))
|
||||
if (!tree) throw new Error("Failed to parse command")
|
||||
return tree.rootNode
|
||||
})
|
||||
|
||||
const ask = Effect.fn("BashTool.ask")(function* (ctx: Tool.Context, scan: Scan) {
|
||||
const ask = Effect.fn("ShellTool.ask")(function* (ctx: Tool.Context, scan: Scan) {
|
||||
if (scan.dirs.size > 0) {
|
||||
const globs = Array.from(scan.dirs).map((dir) => {
|
||||
if (process.platform === "win32") return AppFileSystem.normalizePathPattern(path.join(dir, "*"))
|
||||
|
|
@ -336,7 +336,7 @@ export const ShellTool = Tool.define(
|
|||
const trunc = yield* Truncate.Service
|
||||
const plugin = yield* Plugin.Service
|
||||
|
||||
const cygpath = Effect.fn("BashTool.cygpath")(function* (shell: string, text: string) {
|
||||
const cygpath = Effect.fn("ShellTool.cygpath")(function* (shell: string, text: string) {
|
||||
const lines = yield* spawner
|
||||
.lines(ChildProcess.make(shell, ["-lc", 'cygpath -w -- "$1"', "_", text]))
|
||||
.pipe(Effect.catch(() => Effect.succeed([] as string[])))
|
||||
|
|
@ -345,7 +345,7 @@ export const ShellTool = Tool.define(
|
|||
return AppFileSystem.normalizePath(file)
|
||||
})
|
||||
|
||||
const resolvePath = Effect.fn("BashTool.resolvePath")(function* (text: string, root: string, shell: string) {
|
||||
const resolvePath = Effect.fn("ShellTool.resolvePath")(function* (text: string, root: string, shell: string) {
|
||||
if (process.platform === "win32") {
|
||||
if (Shell.posix(shell) && text.startsWith("/") && AppFileSystem.windowsPath(text) === text) {
|
||||
const file = yield* cygpath(shell, text)
|
||||
|
|
@ -356,7 +356,7 @@ export const ShellTool = Tool.define(
|
|||
return path.resolve(root, text)
|
||||
})
|
||||
|
||||
const argPath = Effect.fn("BashTool.argPath")(function* (arg: string, cwd: string, ps: boolean, shell: string) {
|
||||
const argPath = Effect.fn("ShellTool.argPath")(function* (arg: string, cwd: string, ps: boolean, shell: string) {
|
||||
const text = ps ? expand(arg, cwd, shell) : home(unquote(arg))
|
||||
const file = text && prefix(text)
|
||||
if (!file || dynamic(file, ps)) return
|
||||
|
|
@ -365,7 +365,7 @@ export const ShellTool = Tool.define(
|
|||
return yield* resolvePath(next, cwd, shell)
|
||||
})
|
||||
|
||||
const collect = Effect.fn("BashTool.collect")(function* (root: Node, cwd: string, ps: boolean, shell: string) {
|
||||
const collect = Effect.fn("ShellTool.collect")(function* (root: Node, cwd: string, ps: boolean, shell: string) {
|
||||
const scan: Scan = {
|
||||
dirs: new Set<string>(),
|
||||
patterns: new Set<string>(),
|
||||
|
|
@ -396,7 +396,7 @@ export const ShellTool = Tool.define(
|
|||
return scan
|
||||
})
|
||||
|
||||
const shellEnv = Effect.fn("BashTool.shellEnv")(function* (ctx: Tool.Context, cwd: string) {
|
||||
const shellEnv = Effect.fn("ShellTool.shellEnv")(function* (ctx: Tool.Context, cwd: string) {
|
||||
const extra = yield* plugin.trigger(
|
||||
"shell.env",
|
||||
{ cwd, sessionID: ctx.sessionID, callID: ctx.callID },
|
||||
|
|
@ -408,7 +408,7 @@ export const ShellTool = Tool.define(
|
|||
}
|
||||
})
|
||||
|
||||
const run = Effect.fn("BashTool.run")(function* (
|
||||
const run = Effect.fn("ShellTool.run")(function* (
|
||||
input: {
|
||||
shell: string
|
||||
name: string
|
||||
|
|
@ -644,5 +644,3 @@ export const ShellTool = Tool.define(
|
|||
})
|
||||
}),
|
||||
)
|
||||
|
||||
export const BashTool = ShellTool
|
||||
|
|
@ -21,12 +21,10 @@ export namespace ShellKind {
|
|||
export namespace ShellToolID {
|
||||
export const id = "shell"
|
||||
export const legacy = "bash"
|
||||
export type ID = typeof id | typeof legacy
|
||||
|
||||
const tool = new Set<string>([id, legacy])
|
||||
export type ID = typeof id
|
||||
|
||||
export function has(value: string): value is ID {
|
||||
return tool.has(value)
|
||||
return value === id
|
||||
}
|
||||
|
||||
export function normalize(value: string) {
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
export { ShellTool } from "../bash"
|
||||
|
|
@ -8,7 +8,6 @@ import { Agent } from "../agent/agent"
|
|||
import type { SessionPrompt } from "../session/prompt"
|
||||
import { Config } from "../config"
|
||||
import { Effect } from "effect"
|
||||
import { ShellToolID } from "./shell/id"
|
||||
|
||||
export interface TaskPromptOps {
|
||||
cancel(sessionID: SessionID): void
|
||||
|
|
@ -40,7 +39,7 @@ export const TaskTool = Tool.define(
|
|||
|
||||
const run = Effect.fn("TaskTool.execute")(function* (params: z.infer<typeof parameters>, ctx: Tool.Context) {
|
||||
const cfg = yield* config.get()
|
||||
const primaryTools = (cfg.experimental?.primary_tools ?? []).map((item) => ShellToolID.normalize(item))
|
||||
const primaryTools = cfg.experimental?.primary_tools ?? []
|
||||
|
||||
if (!ctx.extra?.bypassAgentCheck) {
|
||||
yield* ctx.ask({
|
||||
|
|
|
|||
|
|
@ -607,12 +607,12 @@ describe("session.message-v2.toModelMessage", () => {
|
|||
...basePart(assistantID, "a1"),
|
||||
type: "tool",
|
||||
callID: "call-1",
|
||||
tool: "bash",
|
||||
tool: "shell",
|
||||
state: {
|
||||
status: "completed",
|
||||
input: { cmd: "ls" },
|
||||
output: "abcdefghij",
|
||||
title: "Bash",
|
||||
title: "Shell",
|
||||
metadata: {},
|
||||
time: { start: 0, end: 1 },
|
||||
},
|
||||
|
|
@ -755,7 +755,7 @@ describe("session.message-v2.toModelMessage", () => {
|
|||
...basePart(assistantID, "a1"),
|
||||
type: "tool",
|
||||
callID: "call-1",
|
||||
tool: "bash",
|
||||
tool: "shell",
|
||||
state: {
|
||||
status: "error",
|
||||
input: { command: "for i in {1..20}; do print -- $RANDOM; sleep 1; done" },
|
||||
|
|
|
|||
|
|
@ -414,13 +414,13 @@ describe("session-entry-stepper", () => {
|
|||
(callID, title, input, output, metadata, attachments, parts) => {
|
||||
const next = run(
|
||||
[
|
||||
SessionEvent.Tool.Input.Started.create({ callID, name: "bash", timestamp: time(1) }),
|
||||
SessionEvent.Tool.Input.Started.create({ callID, name: "shell", timestamp: time(1) }),
|
||||
...parts.map((x, i) =>
|
||||
SessionEvent.Tool.Input.Delta.create({ callID, delta: x, timestamp: time(i + 2) }),
|
||||
),
|
||||
SessionEvent.Tool.Called.create({
|
||||
callID,
|
||||
tool: "bash",
|
||||
tool: "shell",
|
||||
input,
|
||||
provider: { executed: true },
|
||||
timestamp: time(parts.length + 2),
|
||||
|
|
@ -459,10 +459,10 @@ describe("session-entry-stepper", () => {
|
|||
FastCheck.property(word, dict, word, maybe(dict), (callID, input, error, metadata) => {
|
||||
const next = run(
|
||||
[
|
||||
SessionEvent.Tool.Input.Started.create({ callID, name: "bash", timestamp: time(1) }),
|
||||
SessionEvent.Tool.Input.Started.create({ callID, name: "shell", timestamp: time(1) }),
|
||||
SessionEvent.Tool.Called.create({
|
||||
callID,
|
||||
tool: "bash",
|
||||
tool: "shell",
|
||||
input,
|
||||
provider: { executed: true },
|
||||
timestamp: time(2),
|
||||
|
|
@ -496,7 +496,7 @@ describe("session-entry-stepper", () => {
|
|||
FastCheck.property(word, word, (callID, title) => {
|
||||
const next = run(
|
||||
[
|
||||
SessionEvent.Tool.Input.Started.create({ callID, name: "bash", timestamp: time(1) }),
|
||||
SessionEvent.Tool.Input.Started.create({ callID, name: "shell", timestamp: time(1) }),
|
||||
SessionEvent.Tool.Success.create({
|
||||
callID,
|
||||
title,
|
||||
|
|
@ -691,10 +691,10 @@ describe("session-entry-stepper", () => {
|
|||
SessionEvent.Reasoning.Started.create({ timestamp: time(2) }),
|
||||
...reason.map((x, i) => SessionEvent.Reasoning.Delta.create({ delta: x, timestamp: time(i + 3) })),
|
||||
SessionEvent.Reasoning.Ended.create({ text: end, timestamp: time(reason.length + 3) }),
|
||||
SessionEvent.Tool.Input.Started.create({ callID, name: "bash", timestamp: time(reason.length + 4) }),
|
||||
SessionEvent.Tool.Input.Started.create({ callID, name: "shell", timestamp: time(reason.length + 4) }),
|
||||
SessionEvent.Tool.Called.create({
|
||||
callID,
|
||||
tool: "bash",
|
||||
tool: "shell",
|
||||
input,
|
||||
provider: { executed: true },
|
||||
timestamp: time(reason.length + 5),
|
||||
|
|
@ -771,10 +771,10 @@ describe("session-entry-stepper", () => {
|
|||
FastCheck.property(dict, dict, word, word, (a, b, title, error) => {
|
||||
const next = run(
|
||||
[
|
||||
SessionEvent.Tool.Input.Started.create({ callID: "a", name: "bash", timestamp: time(1) }),
|
||||
SessionEvent.Tool.Input.Started.create({ callID: "a", name: "shell", timestamp: time(1) }),
|
||||
SessionEvent.Tool.Called.create({
|
||||
callID: "a",
|
||||
tool: "bash",
|
||||
tool: "shell",
|
||||
input: a,
|
||||
provider: { executed: true },
|
||||
timestamp: time(2),
|
||||
|
|
@ -789,7 +789,7 @@ describe("session-entry-stepper", () => {
|
|||
SessionEvent.Tool.Input.Started.create({ callID: "b", name: "grep", timestamp: time(4) }),
|
||||
SessionEvent.Tool.Called.create({
|
||||
callID: "b",
|
||||
tool: "bash",
|
||||
tool: "shell",
|
||||
input: b,
|
||||
provider: { executed: true },
|
||||
timestamp: time(5),
|
||||
|
|
@ -827,13 +827,13 @@ describe("session-entry-stepper", () => {
|
|||
FastCheck.property(dict, dict, word, word, text, text, (a, b, titleA, titleB, deltaA, deltaB) => {
|
||||
const next = run(
|
||||
[
|
||||
SessionEvent.Tool.Input.Started.create({ callID: "a", name: "bash", timestamp: time(1) }),
|
||||
SessionEvent.Tool.Input.Started.create({ callID: "a", name: "shell", timestamp: time(1) }),
|
||||
SessionEvent.Tool.Input.Started.create({ callID: "b", name: "grep", timestamp: time(2) }),
|
||||
SessionEvent.Tool.Input.Delta.create({ callID: "a", delta: deltaA, timestamp: time(3) }),
|
||||
SessionEvent.Tool.Input.Delta.create({ callID: "b", delta: deltaB, timestamp: time(4) }),
|
||||
SessionEvent.Tool.Called.create({
|
||||
callID: "a",
|
||||
tool: "bash",
|
||||
tool: "shell",
|
||||
input: a,
|
||||
provider: { executed: true },
|
||||
timestamp: time(5),
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import os from "os"
|
|||
import path from "path"
|
||||
import { Shell } from "../../src/shell/shell"
|
||||
import { ShellToolID } from "../../src/tool/shell/id"
|
||||
import { ShellTool } from "../../src/tool/shell/tool"
|
||||
import { ShellTool } from "../../src/tool/shell"
|
||||
import { Instance } from "../../src/project/instance"
|
||||
import { Filesystem } from "../../src/util"
|
||||
import { tmpdir } from "../fixture/fixture"
|
||||
|
|
|
|||
|
|
@ -378,7 +378,7 @@ describe("tool.task", () => {
|
|||
},
|
||||
},
|
||||
experimental: {
|
||||
primary_tools: ["bash", "read"],
|
||||
primary_tools: ["shell", "read"],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ export function ToolErrorCard(props: ToolErrorCardProps) {
|
|||
websearch: "ui.tool.websearch",
|
||||
codesearch: "ui.tool.codesearch",
|
||||
shell: "ui.tool.shell",
|
||||
bash: "ui.tool.shell",
|
||||
apply_patch: "ui.tool.patch",
|
||||
question: "ui.tool.questions",
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue