refactor(core): consolidate references (#31539)
This commit is contained in:
parent
0bb677cef9
commit
6566ede935
65 changed files with 687 additions and 2730 deletions
|
|
@ -1,310 +0,0 @@
|
|||
import { afterEach, describe, expect } from "bun:test"
|
||||
import path from "path"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Config } from "../../src/config/config"
|
||||
import { ConfigReference } from "../../src/config/reference"
|
||||
import { RuntimeFlags } from "../../src/effect/runtime-flags"
|
||||
import { Git } from "../../src/git"
|
||||
import { Reference } from "../../src/reference/reference"
|
||||
import { RepositoryCache } from "../../src/reference/repository-cache"
|
||||
import { disposeAllInstances, provideTmpdirInstance, tmpdirScoped } from "../fixture/fixture"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
||||
afterEach(async () => {
|
||||
await disposeAllInstances()
|
||||
})
|
||||
|
||||
const referenceLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
|
||||
Reference.layer.pipe(
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(RepositoryCache.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.layer(flags)),
|
||||
)
|
||||
|
||||
const it = testEffect(
|
||||
Layer.mergeAll(FSUtil.defaultLayer, CrossSpawnSpawner.defaultLayer, Git.defaultLayer, referenceLayer()),
|
||||
)
|
||||
const references = testEffect(
|
||||
Layer.mergeAll(
|
||||
FSUtil.defaultLayer,
|
||||
CrossSpawnSpawner.defaultLayer,
|
||||
Git.defaultLayer,
|
||||
referenceLayer({ experimentalReferences: true }),
|
||||
),
|
||||
)
|
||||
|
||||
const githubBase = <A, E, R>(url: string, self: Effect.Effect<A, E, R>) =>
|
||||
Effect.acquireUseRelease(
|
||||
Effect.sync(() => {
|
||||
const previous = process.env.OPENCODE_REPO_CLONE_GITHUB_BASE_URL
|
||||
process.env.OPENCODE_REPO_CLONE_GITHUB_BASE_URL = url
|
||||
return previous
|
||||
}),
|
||||
() => self,
|
||||
(previous) =>
|
||||
Effect.sync(() => {
|
||||
if (previous) process.env.OPENCODE_REPO_CLONE_GITHUB_BASE_URL = previous
|
||||
else delete process.env.OPENCODE_REPO_CLONE_GITHUB_BASE_URL
|
||||
}),
|
||||
)
|
||||
|
||||
const git = Effect.fn("ReferenceTest.git")(function* (cwd: string, args: string[]) {
|
||||
return yield* Effect.promise(async () => {
|
||||
const proc = Bun.spawn(["git", ...args], {
|
||||
cwd,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
})
|
||||
const [stdout, stderr, code] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
])
|
||||
if (code !== 0) throw new Error(stderr.trim() || stdout.trim() || `git ${args.join(" ")} failed`)
|
||||
return stdout.trim()
|
||||
})
|
||||
})
|
||||
|
||||
const waitForContent = (
|
||||
fs: FSUtil.Interface,
|
||||
file: string,
|
||||
content: string,
|
||||
attempts = 50,
|
||||
): Effect.Effect<void, FSUtil.Error> =>
|
||||
Effect.gen(function* () {
|
||||
if ((yield* fs.readFileStringSafe(file)) === content) return
|
||||
if (attempts <= 0) throw new Error(`timed out waiting for ${file}`)
|
||||
yield* Effect.sleep("100 millis")
|
||||
yield* waitForContent(fs, file, content, attempts - 1)
|
||||
})
|
||||
|
||||
describe("reference", () => {
|
||||
it.live("resolves supported local and git config forms", () =>
|
||||
Effect.gen(function* () {
|
||||
const root = path.resolve("opencode-reference-root")
|
||||
const local = Reference.resolve({
|
||||
name: "docs",
|
||||
reference: ConfigReference.normalizeEntry({ path: "../docs" }),
|
||||
directory: path.join(root, "packages", "app"),
|
||||
worktree: root,
|
||||
})
|
||||
const repo = Reference.resolve({
|
||||
name: "effect",
|
||||
reference: ConfigReference.normalizeEntry({ repository: "Effect-TS/effect", branch: "main" }),
|
||||
directory: path.join(root, "packages", "app"),
|
||||
worktree: root,
|
||||
})
|
||||
const localString = Reference.resolve({
|
||||
name: "notes",
|
||||
reference: ConfigReference.normalizeEntry("./notes"),
|
||||
directory: path.join(root, "packages", "app"),
|
||||
worktree: root,
|
||||
})
|
||||
const repoString = Reference.resolve({
|
||||
name: "repo",
|
||||
reference: ConfigReference.normalizeEntry("owner/repo"),
|
||||
directory: path.join(root, "packages", "app"),
|
||||
worktree: root,
|
||||
})
|
||||
|
||||
expect(local.kind).toBe("local")
|
||||
if (local.kind === "local") expect(local.path).toBe(path.resolve(root, "../docs"))
|
||||
expect(localString.kind).toBe("local")
|
||||
if (localString.kind === "local") expect(localString.path).toBe(path.resolve(root, "notes"))
|
||||
expect(repo.kind).toBe("git")
|
||||
if (repo.kind === "git") {
|
||||
expect(repo.repository).toBe("Effect-TS/effect")
|
||||
expect(repo.branch).toBe("main")
|
||||
expect(repo.path).toBe(path.join(Global.Path.repos, "github.com", "Effect-TS", "effect"))
|
||||
}
|
||||
expect(repoString.kind).toBe("git")
|
||||
if (repoString.kind === "git") {
|
||||
expect(repoString.repository).toBe("owner/repo")
|
||||
expect(repoString.path).toBe(path.join(Global.Path.repos, "github.com", "owner", "repo"))
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("keeps invalid repository references visible without materializing", () =>
|
||||
provideTmpdirInstance(
|
||||
(_dir) =>
|
||||
Effect.gen(function* () {
|
||||
const reference = yield* Reference.Service
|
||||
const references = yield* reference.list()
|
||||
const invalid = yield* reference.get("bad")
|
||||
|
||||
expect(references.map((item) => item.name)).toEqual(["bad"])
|
||||
expect(invalid).toMatchObject({
|
||||
name: "bad",
|
||||
kind: "invalid",
|
||||
repository: "not-a-repo",
|
||||
})
|
||||
if (invalid?.kind === "invalid") expect(invalid.message).toContain("Repository must be a git URL")
|
||||
}),
|
||||
{
|
||||
config: {
|
||||
reference: {
|
||||
bad: "not-a-repo",
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
it.live("marks same-cache references with different branches invalid", () =>
|
||||
Effect.gen(function* () {
|
||||
const root = path.resolve("opencode-reference-root")
|
||||
const references = Reference.resolveAll({
|
||||
directory: root,
|
||||
worktree: root,
|
||||
references: ConfigReference.normalize({
|
||||
main: { repository: "owner/repo", branch: "main" },
|
||||
dev: { repository: "github.com/owner/repo", branch: "dev" },
|
||||
alsoMain: { repository: "https://github.com/owner/repo", branch: "main" },
|
||||
}),
|
||||
})
|
||||
|
||||
expect(references.map((reference) => reference.kind)).toEqual(["git", "invalid", "git"])
|
||||
expect(references[1]?.kind).toBe("invalid")
|
||||
if (references[1]?.kind === "invalid") {
|
||||
expect(references[1].message).toContain("conflicts with @main")
|
||||
expect(references[1].message).toContain("@dev requests dev")
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("represents invalid aliases as invalid references", () =>
|
||||
Effect.gen(function* () {
|
||||
const root = path.resolve("opencode-reference-root")
|
||||
const references = Reference.resolveAll({
|
||||
directory: root,
|
||||
worktree: root,
|
||||
references: ConfigReference.normalize({
|
||||
"bad/name": "owner/repo",
|
||||
}),
|
||||
})
|
||||
|
||||
expect(references).toEqual([
|
||||
{
|
||||
name: "bad/name",
|
||||
kind: "invalid",
|
||||
message: "Reference alias must not contain /, whitespace, comma, or backtick",
|
||||
},
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
||||
references.live("materializes configured git references during init", () =>
|
||||
provideTmpdirInstance(
|
||||
(_dir) =>
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FSUtil.Service
|
||||
const cache = path.join(Global.Path.repos, "github.com", "opencode-reference-test", "repo")
|
||||
yield* fs.remove(cache, { recursive: true }).pipe(Effect.ignore)
|
||||
yield* Effect.addFinalizer(() => fs.remove(cache, { recursive: true }).pipe(Effect.ignore))
|
||||
|
||||
const source = yield* tmpdirScoped({ git: true })
|
||||
const remoteRoot = yield* tmpdirScoped()
|
||||
const remoteDir = path.join(remoteRoot, "opencode-reference-test")
|
||||
const remoteRepo = path.join(remoteDir, "repo.git")
|
||||
|
||||
yield* Effect.promise(() => Bun.write(path.join(source, "README.md"), "configured\n"))
|
||||
yield* git(source, ["add", "."])
|
||||
yield* git(source, ["commit", "-m", "add readme"])
|
||||
yield* fs.makeDirectory(remoteDir, { recursive: true }).pipe(Effect.orDie)
|
||||
yield* git(remoteRoot, ["clone", "--bare", source, remoteRepo])
|
||||
|
||||
const reference = yield* Reference.Service
|
||||
yield* githubBase(
|
||||
`file://${remoteRoot}/`,
|
||||
Effect.gen(function* () {
|
||||
yield* reference.init()
|
||||
yield* waitForContent(fs, path.join(cache, "README.md"), "configured\n")
|
||||
}),
|
||||
)
|
||||
|
||||
expect(yield* fs.existsSafe(path.join(cache, ".git"))).toBe(true)
|
||||
expect(yield* fs.readFileString(path.join(cache, "README.md"))).toBe("configured\n")
|
||||
|
||||
const resolved = yield* reference.get("docs")
|
||||
expect(resolved?.kind).toBe("git")
|
||||
if (resolved?.kind === "git") expect(resolved.path).toBe(cache)
|
||||
}),
|
||||
{
|
||||
config: {
|
||||
reference: {
|
||||
docs: "opencode-reference-test/repo",
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
references.live("refreshes configured git references on new instance init", () =>
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FSUtil.Service
|
||||
const cache = path.join(Global.Path.repos, "github.com", "opencode-reference-refresh", "repo")
|
||||
yield* fs.remove(cache, { recursive: true }).pipe(Effect.ignore)
|
||||
yield* Effect.addFinalizer(() => fs.remove(cache, { recursive: true }).pipe(Effect.ignore))
|
||||
|
||||
const source = yield* tmpdirScoped({ git: true })
|
||||
const remoteRoot = yield* tmpdirScoped()
|
||||
const remoteDir = path.join(remoteRoot, "opencode-reference-refresh")
|
||||
const remoteRepo = path.join(remoteDir, "repo.git")
|
||||
|
||||
yield* Effect.promise(() => Bun.write(path.join(source, "README.md"), "v1\n"))
|
||||
yield* git(source, ["add", "."])
|
||||
yield* git(source, ["commit", "-m", "add readme"])
|
||||
yield* fs.makeDirectory(remoteDir, { recursive: true }).pipe(Effect.orDie)
|
||||
yield* git(remoteRoot, ["clone", "--bare", source, remoteRepo])
|
||||
|
||||
yield* githubBase(
|
||||
`file://${remoteRoot}/`,
|
||||
provideTmpdirInstance(
|
||||
(_dir) =>
|
||||
Effect.gen(function* () {
|
||||
const reference = yield* Reference.Service
|
||||
yield* reference.init()
|
||||
yield* waitForContent(fs, path.join(cache, "README.md"), "v1\n")
|
||||
}),
|
||||
{
|
||||
config: {
|
||||
reference: {
|
||||
docs: "opencode-reference-refresh/repo",
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
const branch = yield* git(source, ["branch", "--show-current"])
|
||||
yield* git(source, ["remote", "add", "origin", remoteRepo])
|
||||
yield* Effect.promise(() => Bun.write(path.join(source, "README.md"), "v2\n"))
|
||||
yield* git(source, ["add", "."])
|
||||
yield* git(source, ["commit", "-m", "update readme"])
|
||||
yield* git(source, ["push", "origin", `${branch}:${branch}`])
|
||||
|
||||
yield* githubBase(
|
||||
`file://${remoteRoot}/`,
|
||||
provideTmpdirInstance(
|
||||
(_dir) =>
|
||||
Effect.gen(function* () {
|
||||
const reference = yield* Reference.Service
|
||||
yield* reference.init()
|
||||
yield* waitForContent(fs, path.join(cache, "README.md"), "v2\n")
|
||||
}),
|
||||
{
|
||||
config: {
|
||||
reference: {
|
||||
docs: "opencode-reference-refresh/repo",
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
|
@ -670,7 +670,7 @@ const scenarios: Scenario[] = [
|
|||
.at((ctx) => ({ path: "/api/fs/read?path=hello.txt", headers: ctx.headers() }))
|
||||
.json(200, locationData(object)),
|
||||
http.protected.get("/api/fs/list", "v2.fs.list").json(200, locationData(array)),
|
||||
http.protected.get("/reference", "reference.list").json(200, array),
|
||||
http.protected.get("/api/reference", "v2.reference.list").json(200, object),
|
||||
http.protected
|
||||
.get("/api/provider/{providerID}", "v2.provider.get")
|
||||
.at((ctx) => ({ path: route("/api/provider/{providerID}", { providerID: "missing" }), headers: ctx.headers() }))
|
||||
|
|
|
|||
|
|
@ -94,17 +94,13 @@ describe("PublicApi OpenAPI v2 errors", () => {
|
|||
}
|
||||
})
|
||||
|
||||
test("documents optional project reference aliases for filesystem reads and lists", () => {
|
||||
test("documents references separately from filesystem routes", () => {
|
||||
const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec
|
||||
|
||||
for (const path of ["/api/fs/read", "/api/fs/list"]) {
|
||||
expect(spec.paths[path]?.get?.parameters, path).toContainEqual({
|
||||
in: "query",
|
||||
name: "reference",
|
||||
required: false,
|
||||
schema: { type: "string" },
|
||||
})
|
||||
expect(spec.paths[path]?.get?.parameters, path).not.toContainEqual(expect.objectContaining({ name: "reference" }))
|
||||
}
|
||||
expect(spec.paths["/api/reference"]?.get).toBeDefined()
|
||||
})
|
||||
|
||||
test("preserves required request bodies for v2 mutations", () => {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ afterEach(async () => {
|
|||
})
|
||||
|
||||
describe("reference HttpApi", () => {
|
||||
test("lists presentation-safe references resolved in the server workspace", async () => {
|
||||
test("lists usable references resolved in the server workspace", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
config: {
|
||||
formatter: false,
|
||||
|
|
@ -24,30 +24,31 @@ describe("reference HttpApi", () => {
|
|||
},
|
||||
})
|
||||
|
||||
const response = await Server.Default().app.request("/reference", {
|
||||
const response = await Server.Default().app.request("/api/reference", {
|
||||
headers: { "x-opencode-directory": tmp.path },
|
||||
})
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(await response.json()).toEqual([
|
||||
{
|
||||
name: "docs",
|
||||
kind: "local",
|
||||
path: path.join(tmp.path, "docs"),
|
||||
},
|
||||
{
|
||||
name: "effect",
|
||||
kind: "git",
|
||||
repository: "Effect-TS/effect",
|
||||
path: path.join(Global.Path.repos, "github.com", "Effect-TS", "effect"),
|
||||
branch: "main",
|
||||
},
|
||||
{
|
||||
name: "bad",
|
||||
kind: "invalid",
|
||||
repository: "not-a-repo",
|
||||
message: "Repository must be a git URL, host/path reference, or GitHub owner/repo shorthand",
|
||||
},
|
||||
])
|
||||
const body = await response.json()
|
||||
expect(body).toMatchObject({ location: { directory: tmp.path } })
|
||||
expect(body.data).toEqual([
|
||||
{
|
||||
name: "docs",
|
||||
path: path.join(tmp.path, "docs"),
|
||||
source: {
|
||||
type: "local",
|
||||
path: path.join(tmp.path, "docs"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "effect",
|
||||
path: path.join(Global.Path.repos, "github.com", "Effect-TS", "effect"),
|
||||
source: {
|
||||
type: "git",
|
||||
repository: "Effect-TS/effect",
|
||||
branch: "main",
|
||||
},
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -50,8 +50,6 @@ import { Truncate } from "@/tool/truncate"
|
|||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { Search } from "@opencode-ai/core/filesystem/search"
|
||||
import { Format } from "../../src/format"
|
||||
import { Reference } from "../../src/reference/reference"
|
||||
import { RepositoryCache } from "../../src/reference/repository-cache"
|
||||
import { TestInstance } from "../fixture/fixture"
|
||||
import { awaitWithTimeout, pollWithTimeout, testEffect } from "../lib/effect"
|
||||
import { reply, TestLLMServer } from "../lib/llm-server"
|
||||
|
|
@ -191,9 +189,7 @@ function makePrompt(input?: { processor?: "blocking" }) {
|
|||
Layer.provide(Skill.defaultLayer),
|
||||
Layer.provide(FetchHttpClient.layer),
|
||||
Layer.provide(CrossSpawnSpawner.defaultLayer),
|
||||
Layer.provide(RepositoryCache.defaultLayer),
|
||||
Layer.provide(Git.defaultLayer),
|
||||
Layer.provide(Reference.defaultLayer),
|
||||
Layer.provide(Search.defaultLayer),
|
||||
Layer.provide(Format.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true })),
|
||||
|
|
@ -219,7 +215,6 @@ function makePrompt(input?: { processor?: "blocking" }) {
|
|||
return SessionPrompt.layer.pipe(
|
||||
Layer.provide(SessionRevert.defaultLayer),
|
||||
Layer.provide(Image.defaultLayer),
|
||||
Layer.provide(Reference.defaultLayer),
|
||||
Layer.provide(summary),
|
||||
Layer.provideMerge(run),
|
||||
Layer.provideMerge(compact),
|
||||
|
|
@ -2021,92 +2016,6 @@ noLLMServer.instance(
|
|||
{ config: cfg },
|
||||
)
|
||||
|
||||
noLLMServer.instance(
|
||||
"resolves configured reference mentions to one root directory attachment",
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const { directory: dir } = yield* TestInstance
|
||||
const docs = path.join(dir, "external-docs")
|
||||
yield* ensureDir(path.join(docs, "guide"))
|
||||
yield* ensureDir(path.join(dir, "docs"))
|
||||
yield* writeText(path.join(docs, "README.md"), "reference readme")
|
||||
yield* writeText(path.join(docs, "guide", "intro.md"), "reference intro")
|
||||
yield* writeText(path.join(dir, "docs", "README.md"), "workspace readme")
|
||||
|
||||
const prompt = yield* SessionPrompt.Service
|
||||
const parts = yield* prompt.resolvePromptParts(
|
||||
"Use @docs and @docs/README.md and @docs/guide and @docs/missing.md and @docs/README.md and @build",
|
||||
)
|
||||
const files = parts.filter((part): part is SessionV1.FilePartInput => part.type === "file")
|
||||
const agents = parts.filter((part): part is SessionV1.AgentPartInput => part.type === "agent")
|
||||
const text = parts.find((part): part is SessionV1.TextPartInput => part.type === "text" && !part.synthetic)
|
||||
|
||||
expect(text?.text).toContain("@docs")
|
||||
expect(files).toHaveLength(1)
|
||||
expect(files[0]).toMatchObject({
|
||||
filename: "docs",
|
||||
mime: "application/x-directory",
|
||||
source: { type: "file", path: "docs", text: { value: "@docs" } },
|
||||
})
|
||||
expect(fileURLToPath(files[0].url)).toBe(docs)
|
||||
expect(agents.map((agent) => agent.name)).toEqual(["build"])
|
||||
}),
|
||||
{
|
||||
config: {
|
||||
...cfg,
|
||||
reference: {
|
||||
docs: "./external-docs",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
noLLMServer.instance(
|
||||
"stores raw reference mentions alongside directory attachments",
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const { directory: dir } = yield* TestInstance
|
||||
const docs = path.join(dir, "external-docs")
|
||||
yield* ensureDir(docs)
|
||||
|
||||
const prompt = yield* SessionPrompt.Service
|
||||
const sessions = yield* Session.Service
|
||||
const session = yield* sessions.create({})
|
||||
const message = yield* prompt.prompt({
|
||||
sessionID: session.id,
|
||||
noReply: true,
|
||||
parts: [{ type: "text", text: "Use @docs for context" }],
|
||||
})
|
||||
|
||||
const stored = yield* MessageV2.get({ sessionID: session.id, messageID: message.info.id })
|
||||
const synthetic = stored.parts.filter(
|
||||
(part): part is SessionV1.TextPart => part.type === "text" && part.synthetic === true,
|
||||
)
|
||||
const files = stored.parts.filter((part): part is SessionV1.FilePart => part.type === "file")
|
||||
const text = stored.parts.find((part): part is SessionV1.TextPart => part.type === "text" && !part.synthetic)
|
||||
|
||||
expect(text?.text).toBe("Use @docs for context")
|
||||
expect(synthetic.some((part) => part.text.includes(JSON.stringify({ filePath: docs })))).toBe(true)
|
||||
expect(files).toHaveLength(1)
|
||||
expect(files[0]).toMatchObject({
|
||||
filename: "docs",
|
||||
mime: "application/x-directory",
|
||||
source: { type: "file", path: "docs", text: { value: "@docs", start: 4, end: 9 } },
|
||||
})
|
||||
expect(fileURLToPath(files[0].url)).toBe(docs)
|
||||
|
||||
yield* sessions.remove(session.id)
|
||||
}),
|
||||
{
|
||||
config: {
|
||||
...cfg,
|
||||
reference: {
|
||||
docs: "./external-docs",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
// Special characters in filenames
|
||||
|
||||
noLLMServer.instance(
|
||||
|
|
|
|||
|
|
@ -59,8 +59,6 @@ import { FSUtil } from "@opencode-ai/core/fs-util"
|
|||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { Search } from "@opencode-ai/core/filesystem/search"
|
||||
import { Format } from "../../src/format"
|
||||
import { Reference } from "../../src/reference/reference"
|
||||
import { RepositoryCache } from "../../src/reference/repository-cache"
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
|
||||
const mcp = Layer.succeed(
|
||||
|
|
@ -136,9 +134,7 @@ function makeHttp() {
|
|||
Layer.provide(Skill.defaultLayer),
|
||||
Layer.provide(FetchHttpClient.layer),
|
||||
Layer.provide(CrossSpawnSpawner.defaultLayer),
|
||||
Layer.provide(RepositoryCache.defaultLayer),
|
||||
Layer.provide(Git.defaultLayer),
|
||||
Layer.provide(Reference.defaultLayer),
|
||||
Layer.provide(Search.defaultLayer),
|
||||
Layer.provide(Format.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true })),
|
||||
|
|
@ -164,7 +160,6 @@ function makeHttp() {
|
|||
SessionPrompt.layer.pipe(
|
||||
Layer.provide(SessionRevert.defaultLayer),
|
||||
Layer.provide(Image.defaultLayer),
|
||||
Layer.provide(Reference.defaultLayer),
|
||||
Layer.provide(SessionSummary.defaultLayer),
|
||||
Layer.provideMerge(run),
|
||||
Layer.provideMerge(compact),
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@ import { Truncate } from "@/tool/truncate"
|
|||
import { Agent } from "../../src/agent/agent"
|
||||
import { TestInstance, tmpdirScoped } from "../fixture/fixture"
|
||||
import { testEffect } from "../lib/effect"
|
||||
import { Reference } from "@/reference/reference"
|
||||
import { RepositoryCache } from "@/reference/repository-cache"
|
||||
import { Config } from "@/config/config"
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { Git } from "@/git"
|
||||
|
|
@ -21,13 +19,6 @@ import { Filesystem } from "@/util/filesystem"
|
|||
import { Permission } from "../../src/permission"
|
||||
import type * as Tool from "../../src/tool/tool"
|
||||
|
||||
const referenceLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
|
||||
Reference.layer.pipe(
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(RepositoryCache.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.layer(flags)),
|
||||
)
|
||||
|
||||
const toolLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
|
||||
Layer.mergeAll(
|
||||
CrossSpawnSpawner.defaultLayer,
|
||||
|
|
@ -36,11 +27,9 @@ const toolLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
|
|||
Truncate.defaultLayer,
|
||||
Agent.defaultLayer,
|
||||
Git.defaultLayer,
|
||||
referenceLayer(flags),
|
||||
)
|
||||
|
||||
const it = testEffect(toolLayer())
|
||||
const references = testEffect(toolLayer({ experimentalReferences: true }))
|
||||
const full = (p: string) => (process.platform === "win32" ? Filesystem.normalizePath(p) : p)
|
||||
|
||||
const ctx = {
|
||||
|
|
@ -145,44 +134,4 @@ describe("tool.glob", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
references.instance(
|
||||
"does not ask for external_directory permission inside configured git references",
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
yield* TestInstance
|
||||
const fs = yield* FSUtil.Service
|
||||
const cache = path.join(Global.Path.repos, "github.com", "opencode-glob-reference", "repo")
|
||||
yield* fs.remove(cache, { recursive: true }).pipe(Effect.ignore)
|
||||
yield* Effect.addFinalizer(() => fs.remove(cache, { recursive: true }).pipe(Effect.ignore))
|
||||
|
||||
const source = yield* tmpdirScoped({ git: true })
|
||||
const remoteRoot = yield* tmpdirScoped()
|
||||
const remoteDir = path.join(remoteRoot, "opencode-glob-reference")
|
||||
const remoteRepo = path.join(remoteDir, "repo.git")
|
||||
yield* fs.writeWithDirs(path.join(source, "src", "index.ts"), "export const value = 1\n")
|
||||
yield* git(source, ["add", "."])
|
||||
yield* git(source, ["commit", "-m", "add source"])
|
||||
yield* fs.makeDirectory(remoteDir, { recursive: true }).pipe(Effect.orDie)
|
||||
yield* git(remoteRoot, ["clone", "--bare", source, remoteRepo])
|
||||
|
||||
const { items, next } = asks()
|
||||
const info = yield* GlobTool
|
||||
const glob = yield* info.init()
|
||||
const result = yield* githubBase(
|
||||
`file://${remoteRoot}/`,
|
||||
glob.execute({ pattern: "*.ts", path: path.join(cache, "src") }, next),
|
||||
)
|
||||
|
||||
expect(result.metadata.count).toBe(1)
|
||||
expect(full(result.output)).toContain(full(path.join(cache, "src", "index.ts")))
|
||||
expect(items.find((item) => item.permission === "external_directory")).toBeUndefined()
|
||||
}),
|
||||
{
|
||||
config: {
|
||||
reference: {
|
||||
docs: "opencode-glob-reference/repo",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ import { Agent } from "../../src/agent/agent"
|
|||
import { Search } from "@opencode-ai/core/filesystem/search"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { testEffect } from "../lib/effect"
|
||||
import { Reference } from "@/reference/reference"
|
||||
import { RepositoryCache } from "@/reference/repository-cache"
|
||||
import { Permission } from "../../src/permission"
|
||||
import type * as Tool from "../../src/tool/tool"
|
||||
import { Config } from "@/config/config"
|
||||
|
|
@ -23,13 +21,6 @@ import { RuntimeFlags } from "@/effect/runtime-flags"
|
|||
import { Git } from "@/git"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
|
||||
const referenceLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
|
||||
Reference.layer.pipe(
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(RepositoryCache.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.layer(flags)),
|
||||
)
|
||||
|
||||
const toolLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
|
||||
Layer.mergeAll(
|
||||
CrossSpawnSpawner.defaultLayer,
|
||||
|
|
@ -38,11 +29,9 @@ const toolLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
|
|||
Truncate.defaultLayer,
|
||||
Agent.defaultLayer,
|
||||
Git.defaultLayer,
|
||||
referenceLayer(flags),
|
||||
)
|
||||
|
||||
const it = testEffect(toolLayer())
|
||||
const references = testEffect(toolLayer({ experimentalReferences: true }))
|
||||
const rooted = testEffect(Layer.mergeAll(toolLayer(), testInstanceStoreLayer))
|
||||
|
||||
const ctx = {
|
||||
|
|
@ -215,52 +204,4 @@ describe("tool.grep", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
references.instance(
|
||||
"does not ask for external_directory permission inside configured git references",
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
yield* TestInstance
|
||||
const appfs = yield* FSUtil.Service
|
||||
const cache = path.join(Global.Path.repos, "github.com", "opencode-grep-reference", "repo")
|
||||
yield* appfs.remove(cache, { recursive: true }).pipe(Effect.ignore)
|
||||
yield* Effect.addFinalizer(() => appfs.remove(cache, { recursive: true }).pipe(Effect.ignore))
|
||||
|
||||
const source = yield* tmpdirScoped({ git: true })
|
||||
const remoteRoot = yield* tmpdirScoped()
|
||||
const remoteDir = path.join(remoteRoot, "opencode-grep-reference")
|
||||
const remoteRepo = path.join(remoteDir, "repo.git")
|
||||
yield* appfs.writeWithDirs(path.join(source, "src", "notes.md"), "needle\n")
|
||||
yield* git(source, ["add", "."])
|
||||
yield* git(source, ["commit", "-m", "add notes"])
|
||||
yield* appfs.makeDirectory(remoteDir, { recursive: true }).pipe(Effect.orDie)
|
||||
yield* git(remoteRoot, ["clone", "--bare", source, remoteRepo])
|
||||
|
||||
const requests: Array<Omit<PermissionV1.Request, "id" | "sessionID" | "tool">> = []
|
||||
const next: Tool.Context = {
|
||||
...ctx,
|
||||
ask: (req) =>
|
||||
Effect.sync(() => {
|
||||
requests.push(req)
|
||||
}),
|
||||
}
|
||||
|
||||
const info = yield* GrepTool
|
||||
const grep = yield* info.init()
|
||||
const result = yield* githubBase(
|
||||
`file://${remoteRoot}/`,
|
||||
grep.execute({ pattern: "needle", path: path.join(cache, "src"), include: "*.md" }, next),
|
||||
)
|
||||
|
||||
expect(result.metadata.matches).toBe(1)
|
||||
expect(full(result.output)).toContain(full(path.join(cache, "src", "notes.md")))
|
||||
expect(requests.find((req) => req.permission === "external_directory")).toBeUndefined()
|
||||
}),
|
||||
{
|
||||
config: {
|
||||
reference: {
|
||||
docs: "opencode-grep-reference/repo",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -25,8 +25,6 @@ import {
|
|||
tmpdirScoped,
|
||||
} from "../fixture/fixture"
|
||||
import { testEffect } from "../lib/effect"
|
||||
import { Reference } from "@/reference/reference"
|
||||
import { RepositoryCache } from "@/reference/repository-cache"
|
||||
|
||||
const FIXTURES_DIR = path.join(import.meta.dir, "fixtures")
|
||||
|
||||
|
|
@ -45,13 +43,6 @@ const ctx = {
|
|||
ask: () => Effect.void,
|
||||
}
|
||||
|
||||
const referenceLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
|
||||
Reference.layer.pipe(
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(RepositoryCache.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.layer(flags)),
|
||||
)
|
||||
|
||||
const readLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
|
||||
Layer.mergeAll(
|
||||
Agent.defaultLayer,
|
||||
|
|
@ -59,13 +50,11 @@ const readLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
|
|||
CrossSpawnSpawner.defaultLayer,
|
||||
Instruction.defaultLayer,
|
||||
LSP.defaultLayer,
|
||||
referenceLayer(flags),
|
||||
Search.defaultLayer,
|
||||
Truncate.defaultLayer,
|
||||
)
|
||||
|
||||
const it = testEffect(Layer.mergeAll(readLayer(), testInstanceStoreLayer))
|
||||
const references = testEffect(Layer.mergeAll(readLayer({ experimentalReferences: true }), testInstanceStoreLayer))
|
||||
|
||||
const init = Effect.fn("ReadToolTest.init")(function* () {
|
||||
const info = yield* ReadTool
|
||||
|
|
@ -266,43 +255,6 @@ describe("tool.read external_directory permission", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
references.live("does not ask for external_directory permission when reading configured references", () =>
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FSUtil.Service
|
||||
const cache = path.join(Global.Path.repos, "github.com", "opencode-read-reference", "repo")
|
||||
yield* fs.remove(cache, { recursive: true }).pipe(Effect.ignore)
|
||||
yield* Effect.addFinalizer(() => fs.remove(cache, { recursive: true }).pipe(Effect.ignore))
|
||||
|
||||
const source = yield* tmpdirScoped({ git: true })
|
||||
const remoteRoot = yield* tmpdirScoped()
|
||||
const remoteDir = path.join(remoteRoot, "opencode-read-reference")
|
||||
const remoteRepo = path.join(remoteDir, "repo.git")
|
||||
yield* put(path.join(source, "notes.md"), "reference notes")
|
||||
yield* git(source, ["add", "."])
|
||||
yield* git(source, ["commit", "-m", "add notes"])
|
||||
yield* fs.makeDirectory(remoteDir, { recursive: true }).pipe(Effect.orDie)
|
||||
yield* git(remoteRoot, ["clone", "--bare", source, remoteRepo])
|
||||
|
||||
const dir = yield* tmpdirScoped({
|
||||
git: true,
|
||||
config: {
|
||||
reference: {
|
||||
docs: "opencode-read-reference/repo",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const { items, next } = asks()
|
||||
const result = yield* githubBase(
|
||||
`file://${remoteRoot}/`,
|
||||
exec(dir, { filePath: path.join(cache, "notes.md") }, next),
|
||||
)
|
||||
const ext = items.find((item) => item.permission === "external_directory")
|
||||
|
||||
expect(result.output).toContain("reference notes")
|
||||
expect(ext).toBeUndefined()
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
describe("tool.read env file permissions", () => {
|
||||
|
|
|
|||
|
|
@ -29,8 +29,6 @@ import { Format } from "@/format"
|
|||
import { Search } from "@opencode-ai/core/filesystem/search"
|
||||
import * as Truncate from "@/tool/truncate"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { Reference } from "@/reference/reference"
|
||||
import { RepositoryCache } from "@/reference/repository-cache"
|
||||
|
||||
import { ToolJsonSchema } from "@/tool/json-schema"
|
||||
import { MessageID, SessionID } from "@/session/schema"
|
||||
|
|
@ -60,8 +58,7 @@ const registryLayer = (opts: RegistryLayerOptions = {}) =>
|
|||
Layer.provide(Session.defaultLayer),
|
||||
Layer.provide(Layer.mergeAll(SessionStatus.defaultLayer, BackgroundJob.defaultLayer)),
|
||||
Layer.provide(Provider.defaultLayer),
|
||||
Layer.provide(Layer.mergeAll(Git.defaultLayer, RepositoryCache.defaultLayer)),
|
||||
Layer.provide(Reference.defaultLayer),
|
||||
Layer.provide(Git.defaultLayer),
|
||||
Layer.provide(LSP.defaultLayer),
|
||||
Layer.provide(Instruction.defaultLayer),
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue