mirror of
https://github.com/aljazceru/opencode.git
synced 2026-01-05 00:44:54 +01:00
sync
This commit is contained in:
12
packages/opencode/src/config/markdown.ts
Normal file
12
packages/opencode/src/config/markdown.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export namespace ConfigMarkdown {
|
||||
export const FILE_REGEX = /(?<![\w`])@(\.?[^\s`,.]*(?:\.[^\s`,.]+)*)/g
|
||||
export const SHELL_REGEX = /`[^`]+`/g
|
||||
|
||||
export function files(template: string) {
|
||||
return Array.from(template.matchAll(FILE_REGEX))
|
||||
}
|
||||
|
||||
export function shell(template: string) {
|
||||
return Array.from(template.matchAll(SHELL_REGEX))
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,7 @@ import { ulid } from "ulid"
|
||||
import { spawn } from "child_process"
|
||||
import { Command } from "../command"
|
||||
import { $ } from "bun"
|
||||
import { ConfigMarkdown } from "../config/markdown"
|
||||
|
||||
export namespace SessionPrompt {
|
||||
const log = Log.create({ service: "session.prompt" })
|
||||
@@ -1364,7 +1365,6 @@ export namespace SessionPrompt {
|
||||
* Matches @ followed by file paths, excluding commas, periods at end of sentences, and backticks
|
||||
* Does not match when preceded by word characters or backticks (to avoid email addresses and quoted references)
|
||||
*/
|
||||
export const fileRegex = /(?<![\w`])@(\.?[^\s`,.]*(?:\.[^\s`,.]+)*)/g
|
||||
|
||||
export async function command(input: CommandInput) {
|
||||
log.info("command", input)
|
||||
@@ -1373,10 +1373,10 @@ export namespace SessionPrompt {
|
||||
|
||||
let template = command.template.replace("$ARGUMENTS", input.arguments)
|
||||
|
||||
const bash = Array.from(template.matchAll(bashRegex))
|
||||
if (bash.length > 0) {
|
||||
const shell = ConfigMarkdown.shell(template)
|
||||
if (shell.length > 0) {
|
||||
const results = await Promise.all(
|
||||
bash.map(async ([, cmd]) => {
|
||||
shell.map(async ([, cmd]) => {
|
||||
try {
|
||||
return await $`${{ raw: cmd }}`.nothrow().text()
|
||||
} catch (error) {
|
||||
@@ -1395,9 +1395,9 @@ export namespace SessionPrompt {
|
||||
},
|
||||
] as PromptInput["parts"]
|
||||
|
||||
const matches = Array.from(template.matchAll(fileRegex))
|
||||
const files = ConfigMarkdown.files(template)
|
||||
await Promise.all(
|
||||
matches.map(async (match) => {
|
||||
files.map(async (match) => {
|
||||
const name = match[1]
|
||||
const filepath = name.startsWith("~/")
|
||||
? path.join(os.homedir(), name.slice(2))
|
||||
|
||||
@@ -21,21 +21,23 @@ import { Plugin } from "../plugin"
|
||||
|
||||
export namespace ToolRegistry {
|
||||
// Built-in tools that ship with opencode
|
||||
const BUILTIN = [
|
||||
InvalidTool,
|
||||
BashTool,
|
||||
EditTool,
|
||||
WebFetchTool,
|
||||
GlobTool,
|
||||
GrepTool,
|
||||
ListTool,
|
||||
PatchTool,
|
||||
ReadTool,
|
||||
WriteTool,
|
||||
TodoWriteTool,
|
||||
TodoReadTool,
|
||||
TaskTool,
|
||||
]
|
||||
function builtin() {
|
||||
return [
|
||||
InvalidTool,
|
||||
BashTool,
|
||||
EditTool,
|
||||
WebFetchTool,
|
||||
GlobTool,
|
||||
GrepTool,
|
||||
ListTool,
|
||||
PatchTool,
|
||||
ReadTool,
|
||||
WriteTool,
|
||||
TodoWriteTool,
|
||||
TodoReadTool,
|
||||
TaskTool,
|
||||
]
|
||||
}
|
||||
|
||||
export const state = Instance.state(async () => {
|
||||
const custom = [] as Tool.Info[]
|
||||
@@ -91,7 +93,7 @@ export namespace ToolRegistry {
|
||||
|
||||
async function all(): Promise<Tool.Info[]> {
|
||||
const custom = await state().then((x) => x.custom)
|
||||
return [...BUILTIN, ...custom]
|
||||
return [...builtin(), ...custom]
|
||||
}
|
||||
|
||||
export async function ids() {
|
||||
|
||||
353
packages/opencode/test/config/config.test.ts
Normal file
353
packages/opencode/test/config/config.test.ts
Normal file
@@ -0,0 +1,353 @@
|
||||
import { test, expect } from "bun:test"
|
||||
import { Config } from "../../src/config/config"
|
||||
import { Instance } from "../../src/project/instance"
|
||||
import { tmpdir } from "../fixture/fixture"
|
||||
import path from "path"
|
||||
import fs from "fs/promises"
|
||||
|
||||
test("loads config with defaults when no files exist", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const config = await Config.get()
|
||||
expect(config.username).toBeDefined()
|
||||
expect(config.model).toBeDefined()
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("loads JSON config file", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
await Bun.write(
|
||||
path.join(dir, "opencode.json"),
|
||||
JSON.stringify({
|
||||
$schema: "https://opencode.ai/config.json",
|
||||
model: "test/model",
|
||||
username: "testuser",
|
||||
}),
|
||||
)
|
||||
},
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const config = await Config.get()
|
||||
expect(config.model).toBe("test/model")
|
||||
expect(config.username).toBe("testuser")
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("loads JSONC config file", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
await Bun.write(
|
||||
path.join(dir, "opencode.jsonc"),
|
||||
`{
|
||||
// This is a comment
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"model": "test/model",
|
||||
"username": "testuser"
|
||||
}`,
|
||||
)
|
||||
},
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const config = await Config.get()
|
||||
expect(config.model).toBe("test/model")
|
||||
expect(config.username).toBe("testuser")
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("merges multiple config files with correct precedence", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
await Bun.write(
|
||||
path.join(dir, "opencode.jsonc"),
|
||||
JSON.stringify({
|
||||
$schema: "https://opencode.ai/config.json",
|
||||
model: "base",
|
||||
username: "base",
|
||||
}),
|
||||
)
|
||||
await Bun.write(
|
||||
path.join(dir, "opencode.json"),
|
||||
JSON.stringify({
|
||||
$schema: "https://opencode.ai/config.json",
|
||||
model: "override",
|
||||
}),
|
||||
)
|
||||
},
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const config = await Config.get()
|
||||
expect(config.model).toBe("override")
|
||||
expect(config.username).toBe("base")
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("handles environment variable substitution", async () => {
|
||||
const originalEnv = process.env["TEST_VAR"]
|
||||
process.env["TEST_VAR"] = "test_theme"
|
||||
|
||||
try {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
await Bun.write(
|
||||
path.join(dir, "opencode.json"),
|
||||
JSON.stringify({
|
||||
$schema: "https://opencode.ai/config.json",
|
||||
theme: "{env:TEST_VAR}",
|
||||
}),
|
||||
)
|
||||
},
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const config = await Config.get()
|
||||
expect(config.theme).toBe("test_theme")
|
||||
},
|
||||
})
|
||||
} finally {
|
||||
if (originalEnv !== undefined) {
|
||||
process.env["TEST_VAR"] = originalEnv
|
||||
} else {
|
||||
delete process.env["TEST_VAR"]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test("handles file inclusion substitution", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
await Bun.write(path.join(dir, "included.txt"), "test_theme")
|
||||
await Bun.write(
|
||||
path.join(dir, "opencode.json"),
|
||||
JSON.stringify({
|
||||
$schema: "https://opencode.ai/config.json",
|
||||
theme: "{file:included.txt}",
|
||||
}),
|
||||
)
|
||||
},
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const config = await Config.get()
|
||||
expect(config.theme).toBe("test_theme")
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("validates config schema and throws on invalid fields", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
await Bun.write(
|
||||
path.join(dir, "opencode.json"),
|
||||
JSON.stringify({
|
||||
$schema: "https://opencode.ai/config.json",
|
||||
invalid_field: "should cause error",
|
||||
}),
|
||||
)
|
||||
},
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
// Strict schema should throw an error for invalid fields
|
||||
await expect(Config.get()).rejects.toThrow()
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("throws error for invalid JSON", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
await Bun.write(path.join(dir, "opencode.json"), "{ invalid json }")
|
||||
},
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
await expect(Config.get()).rejects.toThrow()
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("handles agent configuration", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
await Bun.write(
|
||||
path.join(dir, "opencode.json"),
|
||||
JSON.stringify({
|
||||
$schema: "https://opencode.ai/config.json",
|
||||
agent: {
|
||||
test_agent: {
|
||||
model: "test/model",
|
||||
temperature: 0.7,
|
||||
description: "test agent",
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
},
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const config = await Config.get()
|
||||
expect(config.agent?.["test_agent"]).toEqual({
|
||||
model: "test/model",
|
||||
temperature: 0.7,
|
||||
description: "test agent",
|
||||
})
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("handles command configuration", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
await Bun.write(
|
||||
path.join(dir, "opencode.json"),
|
||||
JSON.stringify({
|
||||
$schema: "https://opencode.ai/config.json",
|
||||
command: {
|
||||
test_command: {
|
||||
template: "test template",
|
||||
description: "test command",
|
||||
agent: "test_agent",
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
},
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const config = await Config.get()
|
||||
expect(config.command?.["test_command"]).toEqual({
|
||||
template: "test template",
|
||||
description: "test command",
|
||||
agent: "test_agent",
|
||||
})
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("migrates autoshare to share field", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
await Bun.write(
|
||||
path.join(dir, "opencode.json"),
|
||||
JSON.stringify({
|
||||
$schema: "https://opencode.ai/config.json",
|
||||
autoshare: true,
|
||||
}),
|
||||
)
|
||||
},
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const config = await Config.get()
|
||||
expect(config.share).toBe("auto")
|
||||
expect(config.autoshare).toBe(true)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("migrates mode field to agent field", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
await Bun.write(
|
||||
path.join(dir, "opencode.json"),
|
||||
JSON.stringify({
|
||||
$schema: "https://opencode.ai/config.json",
|
||||
mode: {
|
||||
test_mode: {
|
||||
model: "test/model",
|
||||
temperature: 0.5,
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
},
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const config = await Config.get()
|
||||
expect(config.agent?.["test_mode"]).toEqual({
|
||||
model: "test/model",
|
||||
temperature: 0.5,
|
||||
mode: "primary",
|
||||
})
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("loads config from .opencode directory", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
const opencodeDir = path.join(dir, ".opencode")
|
||||
await fs.mkdir(opencodeDir, { recursive: true })
|
||||
const agentDir = path.join(opencodeDir, "agent")
|
||||
await fs.mkdir(agentDir, { recursive: true })
|
||||
|
||||
await Bun.write(
|
||||
path.join(agentDir, "test.md"),
|
||||
`---
|
||||
model: test/model
|
||||
---
|
||||
Test agent prompt`,
|
||||
)
|
||||
},
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const config = await Config.get()
|
||||
expect(config.agent?.["test"]).toEqual({
|
||||
name: "test",
|
||||
model: "test/model",
|
||||
prompt: "Test agent prompt",
|
||||
})
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("updates config and writes to file", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const newConfig = { model: "updated/model" }
|
||||
await Config.update(newConfig as any)
|
||||
|
||||
const writtenConfig = JSON.parse(await Bun.file(path.join(tmp.path, "config.json")).text())
|
||||
expect(writtenConfig.model).toBe("updated/model")
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("gets config directories", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const dirs = await Config.directories()
|
||||
expect(dirs.length).toBeGreaterThanOrEqual(1)
|
||||
},
|
||||
})
|
||||
})
|
||||
89
packages/opencode/test/config/markdown.test.ts
Normal file
89
packages/opencode/test/config/markdown.test.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { expect, test } from "bun:test"
|
||||
import { ConfigMarkdown } from "../../src/config/markdown"
|
||||
|
||||
const template = `This is a @valid/path/to/a/file and it should also match at
|
||||
the beginning of a line:
|
||||
|
||||
@another-valid/path/to/a/file
|
||||
|
||||
but this is not:
|
||||
|
||||
- Adds a "Co-authored-by:" footer which clarifies which AI agent
|
||||
helped create this commit, using an appropriate \`noreply@...\`
|
||||
or \`noreply@anthropic.com\` email address.
|
||||
|
||||
We also need to deal with files followed by @commas, ones
|
||||
with @file-extensions.md, even @multiple.extensions.bak,
|
||||
hidden directorys like @.config/ or files like @.bashrc
|
||||
and ones at the end of a sentence like @foo.md.
|
||||
|
||||
Also shouldn't forget @/absolute/paths.txt with and @/without/extensions,
|
||||
as well as @~/home-files and @~/paths/under/home.txt.
|
||||
|
||||
If the reference is \`@quoted/in/backticks\` then it shouldn't match at all.`
|
||||
|
||||
const matches = ConfigMarkdown.files(template)
|
||||
|
||||
test("should extract exactly 12 file references", () => {
|
||||
expect(matches.length).toBe(12)
|
||||
})
|
||||
|
||||
test("should extract valid/path/to/a/file", () => {
|
||||
expect(matches[0][1]).toBe("valid/path/to/a/file")
|
||||
})
|
||||
|
||||
test("should extract another-valid/path/to/a/file", () => {
|
||||
expect(matches[1][1]).toBe("another-valid/path/to/a/file")
|
||||
})
|
||||
|
||||
test("should extract paths ignoring comma after", () => {
|
||||
expect(matches[2][1]).toBe("commas")
|
||||
})
|
||||
|
||||
test("should extract a path with a file extension and comma after", () => {
|
||||
expect(matches[3][1]).toBe("file-extensions.md")
|
||||
})
|
||||
|
||||
test("should extract a path with multiple dots and comma after", () => {
|
||||
expect(matches[4][1]).toBe("multiple.extensions.bak")
|
||||
})
|
||||
|
||||
test("should extract hidden directory", () => {
|
||||
expect(matches[5][1]).toBe(".config/")
|
||||
})
|
||||
|
||||
test("should extract hidden file", () => {
|
||||
expect(matches[6][1]).toBe(".bashrc")
|
||||
})
|
||||
|
||||
test("should extract a file ignoring period at end of sentence", () => {
|
||||
expect(matches[7][1]).toBe("foo.md")
|
||||
})
|
||||
|
||||
test("should extract an absolute path with an extension", () => {
|
||||
expect(matches[8][1]).toBe("/absolute/paths.txt")
|
||||
})
|
||||
|
||||
test("should extract an absolute path without an extension", () => {
|
||||
expect(matches[9][1]).toBe("/without/extensions")
|
||||
})
|
||||
|
||||
test("should extract an absolute path in home directory", () => {
|
||||
expect(matches[10][1]).toBe("~/home-files")
|
||||
})
|
||||
|
||||
test("should extract an absolute path under home directory", () => {
|
||||
expect(matches[11][1]).toBe("~/paths/under/home.txt")
|
||||
})
|
||||
|
||||
test("should not match when preceded by backtick", () => {
|
||||
const backtickTest = "This `@should/not/match` should be ignored"
|
||||
const backtickMatches = ConfigMarkdown.files(backtickTest)
|
||||
expect(backtickMatches.length).toBe(0)
|
||||
})
|
||||
|
||||
test("should not match email addresses", () => {
|
||||
const emailTest = "Contact user@example.com for help"
|
||||
const emailMatches = ConfigMarkdown.files(emailTest)
|
||||
expect(emailMatches.length).toBe(0)
|
||||
})
|
||||
@@ -2,12 +2,12 @@ import { $ } from "bun"
|
||||
import os from "os"
|
||||
import path from "path"
|
||||
|
||||
type TmpDirOptions<Init extends Record<string, any>> = {
|
||||
type TmpDirOptions<T> = {
|
||||
git?: boolean
|
||||
init?: (dir: string) => Promise<Init>
|
||||
dispose?: (dir: string) => Promise<void>
|
||||
init?: (dir: string) => Promise<T>
|
||||
dispose?: (dir: string) => Promise<T>
|
||||
}
|
||||
export async function tmpdir<Init extends Record<string, any>>(options?: TmpDirOptions<Init>) {
|
||||
export async function tmpdir<T>(options?: TmpDirOptions<T>) {
|
||||
const dirpath = path.join(os.tmpdir(), "opencode-test-" + Math.random().toString(36).slice(2))
|
||||
await $`mkdir -p ${dirpath}`.quiet()
|
||||
if (options?.git) await $`git init`.cwd(dirpath).quiet()
|
||||
@@ -18,7 +18,7 @@ export async function tmpdir<Init extends Record<string, any>>(options?: TmpDirO
|
||||
await $`rm -rf ${dirpath}`.quiet()
|
||||
},
|
||||
path: dirpath,
|
||||
extra: extra as Init,
|
||||
extra: extra as T,
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { SessionPrompt } from "../../src/session/prompt"
|
||||
|
||||
describe("fileRegex", () => {
|
||||
const template = `This is a @valid/path/to/a/file and it should also match at
|
||||
the beginning of a line:
|
||||
|
||||
@another-valid/path/to/a/file
|
||||
|
||||
but this is not:
|
||||
|
||||
- Adds a "Co-authored-by:" footer which clarifies which AI agent
|
||||
helped create this commit, using an appropriate \`noreply@...\`
|
||||
or \`noreply@anthropic.com\` email address.
|
||||
|
||||
We also need to deal with files followed by @commas, ones
|
||||
with @file-extensions.md, even @multiple.extensions.bak,
|
||||
hidden directorys like @.config/ or files like @.bashrc
|
||||
and ones at the end of a sentence like @foo.md.
|
||||
|
||||
Also shouldn't forget @/absolute/paths.txt with and @/without/extensions,
|
||||
as well as @~/home-files and @~/paths/under/home.txt.
|
||||
|
||||
If the reference is \`@quoted/in/backticks\` then it shouldn't match at all.`
|
||||
|
||||
const matches = Array.from(template.matchAll(SessionPrompt.fileRegex))
|
||||
|
||||
test("should extract exactly 12 file references", () => {
|
||||
expect(matches.length).toBe(12)
|
||||
})
|
||||
|
||||
test("should extract valid/path/to/a/file", () => {
|
||||
expect(matches[0][1]).toBe("valid/path/to/a/file")
|
||||
})
|
||||
|
||||
test("should extract another-valid/path/to/a/file", () => {
|
||||
expect(matches[1][1]).toBe("another-valid/path/to/a/file")
|
||||
})
|
||||
|
||||
test("should extract paths ignoring comma after", () => {
|
||||
expect(matches[2][1]).toBe("commas")
|
||||
})
|
||||
|
||||
test("should extract a path with a file extension and comma after", () => {
|
||||
expect(matches[3][1]).toBe("file-extensions.md")
|
||||
})
|
||||
|
||||
test("should extract a path with multiple dots and comma after", () => {
|
||||
expect(matches[4][1]).toBe("multiple.extensions.bak")
|
||||
})
|
||||
|
||||
test("should extract hidden directory", () => {
|
||||
expect(matches[5][1]).toBe(".config/")
|
||||
})
|
||||
|
||||
test("should extract hidden file", () => {
|
||||
expect(matches[6][1]).toBe(".bashrc")
|
||||
})
|
||||
|
||||
test("should extract a file ignoring period at end of sentence", () => {
|
||||
expect(matches[7][1]).toBe("foo.md")
|
||||
})
|
||||
|
||||
test("should extract an absolute path with an extension", () => {
|
||||
expect(matches[8][1]).toBe("/absolute/paths.txt")
|
||||
})
|
||||
|
||||
test("should extract an absolute path without an extension", () => {
|
||||
expect(matches[9][1]).toBe("/without/extensions")
|
||||
})
|
||||
|
||||
test("should extract an absolute path in home directory", () => {
|
||||
expect(matches[10][1]).toBe("~/home-files")
|
||||
})
|
||||
|
||||
test("should extract an absolute path under home directory", () => {
|
||||
expect(matches[11][1]).toBe("~/paths/under/home.txt")
|
||||
})
|
||||
|
||||
test("should not match when preceded by backtick", () => {
|
||||
const backtickTest = "This `@should/not/match` should be ignored"
|
||||
const backtickMatches = Array.from(backtickTest.matchAll(SessionPrompt.fileRegex))
|
||||
expect(backtickMatches.length).toBe(0)
|
||||
})
|
||||
|
||||
test("should not match email addresses", () => {
|
||||
const emailTest = "Contact user@example.com for help"
|
||||
const emailMatches = Array.from(emailTest.matchAll(SessionPrompt.fileRegex))
|
||||
expect(emailMatches.length).toBe(0)
|
||||
})
|
||||
})
|
||||
@@ -1,70 +0,0 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { GlobTool } from "../../src/tool/glob"
|
||||
import { ListTool } from "../../src/tool/ls"
|
||||
import path from "path"
|
||||
import { Instance } from "../../src/project/instance"
|
||||
|
||||
const ctx = {
|
||||
sessionID: "test",
|
||||
messageID: "",
|
||||
toolCallID: "",
|
||||
agent: "build",
|
||||
abort: AbortSignal.any([]),
|
||||
metadata: () => {},
|
||||
}
|
||||
const glob = await GlobTool.init()
|
||||
const list = await ListTool.init()
|
||||
|
||||
const projectRoot = path.join(__dirname, "../..")
|
||||
const fixturePath = path.join(__dirname, "../fixtures/example")
|
||||
|
||||
describe("tool.glob", () => {
|
||||
test("truncate", async () => {
|
||||
await Instance.provide({
|
||||
directory: projectRoot,
|
||||
fn: async () => {
|
||||
let result = await glob.execute(
|
||||
{
|
||||
pattern: "**/*",
|
||||
path: "../../node_modules",
|
||||
},
|
||||
ctx,
|
||||
)
|
||||
expect(result.metadata.truncated).toBe(true)
|
||||
},
|
||||
})
|
||||
})
|
||||
test("basic", async () => {
|
||||
await Instance.provide({
|
||||
directory: projectRoot,
|
||||
fn: async () => {
|
||||
let result = await glob.execute(
|
||||
{
|
||||
pattern: "*.json",
|
||||
path: undefined,
|
||||
},
|
||||
ctx,
|
||||
)
|
||||
expect(result.metadata).toMatchObject({
|
||||
truncated: false,
|
||||
count: 2,
|
||||
})
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("tool.ls", () => {
|
||||
test("basic", async () => {
|
||||
const result = await Instance.provide({
|
||||
directory: projectRoot,
|
||||
fn: async () => {
|
||||
return await list.execute({ path: fixturePath, ignore: [".git"] }, ctx)
|
||||
},
|
||||
})
|
||||
|
||||
// Normalize absolute path to relative for consistent snapshots
|
||||
const normalizedOutput = result.output.replace(fixturePath, "packages/opencode/test/fixtures/example")
|
||||
expect(normalizedOutput).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user