feat(opencode): enhance platform-specific command handling and environment variable syntax
- Introduced `shellQuoteForPlatform` to build shell-safe command strings tailored for Windows and POSIX environments. - Updated `buildSandboxEnvPrompt` to display the correct environment variable syntax based on the operating system. - Refactored the command copying logic in the runner to use platform-aware commands for copying custom OpenCode tools. - Removed outdated test scenarios related to the vision connector, streamlining the test suite.
This commit is contained in:
parent
4b97a890fd
commit
04c3114344
6 changed files with 348 additions and 70 deletions
|
|
@ -66,7 +66,7 @@ func (r *Runner) buildCommand(req *types.StreamRequest, p platform, attachmentPa
|
|||
|
||||
stdinMsg := buildStdinMessage(req.Messages, attachmentPaths)
|
||||
|
||||
script := shellQuote("opencode", args...)
|
||||
script := shellQuoteForPlatform(p, "opencode", args...)
|
||||
|
||||
return command{
|
||||
shell: p.ShellCmd(script),
|
||||
|
|
@ -255,12 +255,18 @@ func buildSandboxEnvPrompt(p platform, workDir string) string {
|
|||
shell = "bash"
|
||||
}
|
||||
|
||||
envVarSyntax := "$VAR_NAME"
|
||||
if osName == "windows" {
|
||||
envVarSyntax = "$env:VAR_NAME"
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`## Sandbox Environment
|
||||
|
||||
- **Operating System**: %[2]s
|
||||
- **Shell**: %[3]s
|
||||
- **Working Directory**: %[1]s
|
||||
- **File Access**: You have full read/write access to %[1]s
|
||||
- **Environment variable syntax**: `+"`%[4]s`"+`
|
||||
|
||||
## User Attachments
|
||||
|
||||
|
|
@ -268,7 +274,7 @@ User-uploaded files are placed in %[1]s/.attachments/{chatID}/
|
|||
Each chat session has its own subdirectory.
|
||||
When the user attaches files, their paths are listed at the top of the message.
|
||||
**Read these files yourself** using the Read or Bash tool — they are NOT passed as CLI arguments.
|
||||
`, workDir, osName, shell)
|
||||
`, workDir, osName, shell, envVarSyntax)
|
||||
}
|
||||
|
||||
func getProviderPrefix(conn connector.Connector) string {
|
||||
|
|
@ -302,7 +308,17 @@ func getRoleConnectors(req *types.StreamRequest) map[string]*types.RoleConnector
|
|||
return req.Config.Runner.Connectors
|
||||
}
|
||||
|
||||
// shellQuote builds a shell-safe command string from program and args.
|
||||
// shellQuoteForPlatform builds a shell-safe command string. On Windows
|
||||
// (PowerShell) it uses single quotes with ” escaping; on POSIX it uses
|
||||
// single quotes with '\” escaping.
|
||||
func shellQuoteForPlatform(p platform, program string, args ...string) string {
|
||||
if p.OS() == "windows" {
|
||||
return shellQuotePowerShell(program, args...)
|
||||
}
|
||||
return shellQuote(program, args...)
|
||||
}
|
||||
|
||||
// shellQuote builds a POSIX shell-safe command string from program and args.
|
||||
func shellQuote(program string, args ...string) string {
|
||||
parts := make([]string, 0, 1+len(args))
|
||||
parts = append(parts, program)
|
||||
|
|
@ -316,6 +332,21 @@ func shellQuote(program string, args ...string) string {
|
|||
return strings.Join(parts, " ")
|
||||
}
|
||||
|
||||
// shellQuotePowerShell builds a PowerShell-safe command string. In PowerShell,
|
||||
// single-quoted strings escape embedded single quotes by doubling them (”).
|
||||
func shellQuotePowerShell(program string, args ...string) string {
|
||||
parts := make([]string, 0, 1+len(args))
|
||||
parts = append(parts, program)
|
||||
for _, a := range args {
|
||||
if a == "" || strings.ContainsAny(a, " \t\n\"'\\$`!#&|;(){}[]<>?*~") {
|
||||
parts = append(parts, "'"+strings.ReplaceAll(a, "'", "''")+"'")
|
||||
} else {
|
||||
parts = append(parts, a)
|
||||
}
|
||||
}
|
||||
return strings.Join(parts, " ")
|
||||
}
|
||||
|
||||
// connectorModelID returns the "provider/model" string matching the
|
||||
// provider ID used in opencode.json (see buildProviderConfig).
|
||||
func connectorModelID(c connector.Connector) string {
|
||||
|
|
|
|||
76
agent/sandbox/v2/opencode/plat_win.go
Normal file
76
agent/sandbox/v2/opencode/plat_win.go
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
package opencode
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// windowsPlatform implements the platform interface for Windows containers.
|
||||
// Aligned with the Claude runner's plat_win.go: uses PowerShell for shell
|
||||
// commands, backslash path joins, and full HOME-related env vars.
|
||||
type windowsPlatform struct {
|
||||
workDir string
|
||||
shell string
|
||||
}
|
||||
|
||||
func newWindowsPlatform(workDir, shell string) *windowsPlatform {
|
||||
if shell == "" {
|
||||
shell = "pwsh"
|
||||
}
|
||||
return &windowsPlatform{workDir: workDir, shell: shell}
|
||||
}
|
||||
|
||||
func (w *windowsPlatform) OS() string { return "windows" }
|
||||
func (w *windowsPlatform) Shell() string { return w.shell }
|
||||
|
||||
func (w *windowsPlatform) PathJoin(parts ...string) string {
|
||||
return strings.Join(parts, `\`)
|
||||
}
|
||||
|
||||
// HomeEnv sets HOME, USERPROFILE, HOMEDRIVE, and HOMEPATH so that Git for
|
||||
// Windows, npm, and other tools resolve ~ correctly inside the container.
|
||||
// See Claude runner plat_win.go and anthropics/claude-code#13138.
|
||||
func (w *windowsPlatform) HomeEnv(workDir string) map[string]string {
|
||||
env := map[string]string{
|
||||
"HOME": workDir,
|
||||
"USERPROFILE": workDir,
|
||||
}
|
||||
if len(workDir) >= 2 && workDir[1] == ':' {
|
||||
env["HOMEDRIVE"] = workDir[:2]
|
||||
env["HOMEPATH"] = workDir[2:]
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
func (w *windowsPlatform) ShellCmd(script string) []string {
|
||||
shell := strings.ToLower(w.shell)
|
||||
switch shell {
|
||||
case "pwsh":
|
||||
return []string{"pwsh", "-NoProfile", "-Command", script}
|
||||
case "powershell":
|
||||
return []string{"powershell", "-NoProfile", "-Command", script}
|
||||
case "cmd.exe", "cmd":
|
||||
return []string{"cmd.exe", "/C", script}
|
||||
default:
|
||||
return []string{"pwsh", "-NoProfile", "-Command", script}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *windowsPlatform) KillCmd(pattern string) []string {
|
||||
script := fmt.Sprintf(
|
||||
"Get-Process -ErrorAction SilentlyContinue | Where-Object {$_.ProcessName -like '*%s*'} | "+
|
||||
"ForEach-Object { taskkill /F /T /PID $_.Id 2>$null }; "+
|
||||
"Get-Process -ErrorAction SilentlyContinue | Where-Object {$_.ProcessName -like '*%s*'} | "+
|
||||
"Stop-Process -Force -ErrorAction SilentlyContinue",
|
||||
pattern, pattern)
|
||||
return w.ShellCmd(script)
|
||||
}
|
||||
|
||||
func (w *windowsPlatform) KillSessionCmd(sessionName string) []string {
|
||||
script := fmt.Sprintf(
|
||||
"Get-Process -ErrorAction SilentlyContinue | "+
|
||||
"Where-Object { $_.CommandLine -like '*%s*' } | "+
|
||||
"ForEach-Object { taskkill /F /T /PID $_.Id 2>$null }",
|
||||
sessionName)
|
||||
return w.ShellCmd(script)
|
||||
}
|
||||
|
|
@ -51,6 +51,10 @@ func resolvePlatform(computer infra.Computer) platform {
|
|||
workDir := computer.GetWorkDir()
|
||||
shell := sys.Shell
|
||||
|
||||
if osName == "windows" {
|
||||
return newWindowsPlatform(workDir, shell)
|
||||
}
|
||||
|
||||
base := posixBase{os: osName, workDir: workDir, shell: shell}
|
||||
if base.shell == "" {
|
||||
base.shell = "bash"
|
||||
|
|
|
|||
220
agent/sandbox/v2/opencode/platform_test.go
Normal file
220
agent/sandbox/v2/opencode/platform_test.go
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
package opencode
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// POSIX posixBase tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestPosixBase_Accessors(t *testing.T) {
|
||||
b := &posixBase{os: "linux", workDir: "/workspace", shell: "bash"}
|
||||
assert.Equal(t, "linux", b.OS())
|
||||
assert.Equal(t, "bash", b.Shell())
|
||||
assert.Equal(t, "/workspace/.yao/config", b.PathJoin("/workspace", ".yao", "config"))
|
||||
}
|
||||
|
||||
func TestPosixBase_HomeEnv(t *testing.T) {
|
||||
b := &posixBase{}
|
||||
env := b.HomeEnv("/workspace")
|
||||
assert.Equal(t, "/workspace", env["HOME"])
|
||||
assert.Len(t, env, 1)
|
||||
}
|
||||
|
||||
func TestPosixBase_ShellCmd(t *testing.T) {
|
||||
b := &posixBase{}
|
||||
cmd := b.ShellCmd("echo hello")
|
||||
assert.Equal(t, []string{"bash", "-c", "echo hello"}, cmd)
|
||||
}
|
||||
|
||||
func TestPosixBase_KillCmd(t *testing.T) {
|
||||
b := &posixBase{}
|
||||
cmd := b.KillCmd("opencode")
|
||||
require.Len(t, cmd, 3)
|
||||
assert.Equal(t, "sh", cmd[0])
|
||||
assert.Contains(t, cmd[2], "pkill")
|
||||
assert.Contains(t, cmd[2], "opencode")
|
||||
}
|
||||
|
||||
func TestPosixBase_KillSessionCmd(t *testing.T) {
|
||||
b := &posixBase{}
|
||||
cmd := b.KillSessionCmd("yao-oc-session123")
|
||||
require.Len(t, cmd, 3)
|
||||
assert.Equal(t, "sh", cmd[0])
|
||||
assert.Contains(t, cmd[2], "pkill -9 -f")
|
||||
assert.Contains(t, cmd[2], "yao-oc-session123")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Windows windowsPlatform tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestWindows_NewDefaults(t *testing.T) {
|
||||
w := newWindowsPlatform(`C:\workspace`, "")
|
||||
assert.Equal(t, "pwsh", w.Shell())
|
||||
}
|
||||
|
||||
func TestWindows_Accessors(t *testing.T) {
|
||||
w := newWindowsPlatform(`C:\workspace`, "pwsh")
|
||||
assert.Equal(t, "windows", w.OS())
|
||||
assert.Equal(t, "pwsh", w.Shell())
|
||||
}
|
||||
|
||||
func TestWindows_PathJoin(t *testing.T) {
|
||||
w := newWindowsPlatform(`C:\workspace`, "pwsh")
|
||||
assert.Equal(t, `C:\workspace\.yao\config`, w.PathJoin(`C:\workspace`, ".yao", "config"))
|
||||
assert.Equal(t, `a\b\c`, w.PathJoin("a", "b", "c"))
|
||||
}
|
||||
|
||||
func TestWindows_HomeEnv(t *testing.T) {
|
||||
w := newWindowsPlatform(`C:\workspace`, "pwsh")
|
||||
env := w.HomeEnv(`C:\workspace`)
|
||||
assert.Equal(t, `C:\workspace`, env["HOME"])
|
||||
assert.Equal(t, `C:\workspace`, env["USERPROFILE"])
|
||||
assert.Equal(t, `C:`, env["HOMEDRIVE"])
|
||||
assert.Equal(t, `\workspace`, env["HOMEPATH"])
|
||||
assert.Len(t, env, 4)
|
||||
}
|
||||
|
||||
func TestWindows_HomeEnv_NoDrive(t *testing.T) {
|
||||
w := newWindowsPlatform("X", "pwsh")
|
||||
env := w.HomeEnv("X")
|
||||
assert.Equal(t, "X", env["HOME"])
|
||||
assert.Equal(t, "X", env["USERPROFILE"])
|
||||
_, hasDrive := env["HOMEDRIVE"]
|
||||
assert.False(t, hasDrive, "should not set HOMEDRIVE for path without drive letter")
|
||||
assert.Len(t, env, 2)
|
||||
}
|
||||
|
||||
func TestWindows_ShellCmd_Pwsh(t *testing.T) {
|
||||
w := newWindowsPlatform(`C:\ws`, "pwsh")
|
||||
cmd := w.ShellCmd("echo hello")
|
||||
assert.Equal(t, []string{"pwsh", "-NoProfile", "-Command", "echo hello"}, cmd)
|
||||
}
|
||||
|
||||
func TestWindows_ShellCmd_Powershell(t *testing.T) {
|
||||
w := newWindowsPlatform(`C:\ws`, "powershell")
|
||||
cmd := w.ShellCmd("echo hello")
|
||||
assert.Equal(t, "powershell", cmd[0])
|
||||
assert.Equal(t, "-NoProfile", cmd[1])
|
||||
}
|
||||
|
||||
func TestWindows_ShellCmd_Cmd(t *testing.T) {
|
||||
w := newWindowsPlatform(`C:\ws`, "cmd.exe")
|
||||
cmd := w.ShellCmd("echo hello")
|
||||
assert.Equal(t, []string{"cmd.exe", "/C", "echo hello"}, cmd)
|
||||
}
|
||||
|
||||
func TestWindows_ShellCmd_Default(t *testing.T) {
|
||||
w := newWindowsPlatform(`C:\ws`, "unknown-shell")
|
||||
cmd := w.ShellCmd("echo")
|
||||
assert.Equal(t, "pwsh", cmd[0], "unknown shell should fall back to pwsh")
|
||||
}
|
||||
|
||||
func TestWindows_KillCmd(t *testing.T) {
|
||||
w := newWindowsPlatform(`C:\ws`, "pwsh")
|
||||
cmd := w.KillCmd("opencode")
|
||||
require.Len(t, cmd, 4)
|
||||
assert.Equal(t, "pwsh", cmd[0])
|
||||
assert.Contains(t, cmd[3], "opencode")
|
||||
assert.Contains(t, cmd[3], "taskkill")
|
||||
assert.Contains(t, cmd[3], "Stop-Process")
|
||||
}
|
||||
|
||||
func TestWindows_KillSessionCmd(t *testing.T) {
|
||||
w := newWindowsPlatform(`C:\ws`, "pwsh")
|
||||
cmd := w.KillSessionCmd("yao-oc-session123")
|
||||
require.Len(t, cmd, 4)
|
||||
assert.Equal(t, "pwsh", cmd[0])
|
||||
assert.Contains(t, cmd[3], "CommandLine")
|
||||
assert.Contains(t, cmd[3], "yao-oc-session123")
|
||||
assert.Contains(t, cmd[3], "taskkill")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// shellQuote / shellQuoteForPlatform tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestShellQuote_POSIX(t *testing.T) {
|
||||
result := shellQuote("opencode", "run", "--format", "json")
|
||||
assert.Equal(t, "opencode run --format json", result)
|
||||
}
|
||||
|
||||
func TestShellQuote_POSIX_SpecialChars(t *testing.T) {
|
||||
result := shellQuote("opencode", "run", "hello world", "it's")
|
||||
assert.Contains(t, result, "'hello world'")
|
||||
assert.Contains(t, result, `'\''`)
|
||||
}
|
||||
|
||||
func TestShellQuotePowerShell(t *testing.T) {
|
||||
result := shellQuotePowerShell("opencode", "run", "--format", "json")
|
||||
assert.Equal(t, "opencode run --format json", result)
|
||||
}
|
||||
|
||||
func TestShellQuotePowerShell_SpecialChars(t *testing.T) {
|
||||
result := shellQuotePowerShell("opencode", "run", "hello world", "it's")
|
||||
assert.Contains(t, result, "'hello world'")
|
||||
assert.Contains(t, result, "'it''s'")
|
||||
assert.NotContains(t, result, `'\''`, "PowerShell should use '' not '\\''")
|
||||
}
|
||||
|
||||
func TestShellQuoteForPlatform_POSIX(t *testing.T) {
|
||||
p := &posixBase{os: "linux"}
|
||||
result := shellQuoteForPlatform(p, "opencode", "it's")
|
||||
assert.Contains(t, result, `'\''`)
|
||||
}
|
||||
|
||||
func TestShellQuoteForPlatform_Windows(t *testing.T) {
|
||||
p := newWindowsPlatform(`C:\ws`, "pwsh")
|
||||
result := shellQuoteForPlatform(p, "opencode", "it's")
|
||||
assert.Contains(t, result, "''s'")
|
||||
assert.NotContains(t, result, `'\''`)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// buildSandboxEnvPrompt tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestBuildSandboxEnvPrompt_Linux(t *testing.T) {
|
||||
p := &posixBase{os: "linux", shell: "bash"}
|
||||
prompt := buildSandboxEnvPrompt(p, "/workspace")
|
||||
assert.Contains(t, prompt, "linux")
|
||||
assert.Contains(t, prompt, "bash")
|
||||
assert.Contains(t, prompt, "/workspace")
|
||||
assert.Contains(t, prompt, "$VAR_NAME")
|
||||
assert.NotContains(t, prompt, "$env:")
|
||||
}
|
||||
|
||||
func TestBuildSandboxEnvPrompt_Windows(t *testing.T) {
|
||||
p := newWindowsPlatform(`C:\workspace`, "pwsh")
|
||||
prompt := buildSandboxEnvPrompt(p, `C:\workspace`)
|
||||
assert.Contains(t, prompt, "windows")
|
||||
assert.Contains(t, prompt, "pwsh")
|
||||
assert.Contains(t, prompt, `C:\workspace`)
|
||||
assert.Contains(t, prompt, "$env:VAR_NAME")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Vision read.ts copy step generation logic
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestVisionCopyStep_Linux(t *testing.T) {
|
||||
p := &posixBase{os: "linux", workDir: "/workspace", shell: "bash"}
|
||||
cmd := visionCopyCmd(p)
|
||||
assert.Contains(t, cmd, "mkdir -p")
|
||||
assert.Contains(t, cmd, "opencode-tools")
|
||||
assert.NotContains(t, cmd, "PowerShell")
|
||||
}
|
||||
|
||||
func TestVisionCopyStep_Windows(t *testing.T) {
|
||||
p := newWindowsPlatform(`C:\workspace`, "pwsh")
|
||||
cmd := visionCopyCmd(p)
|
||||
assert.Contains(t, cmd, "Test-Path")
|
||||
assert.Contains(t, cmd, "Copy-Item")
|
||||
assert.Contains(t, cmd, `opencode-tools`)
|
||||
assert.NotContains(t, cmd, "mkdir -p")
|
||||
}
|
||||
|
|
@ -81,9 +81,10 @@ func (r *Runner) Prepare(ctx context.Context, req *types.PrepareRequest) error {
|
|||
// built-in read to route image files through the vision API.
|
||||
if req.Config != nil && req.Config.Runner.Connectors != nil {
|
||||
if vc, ok := req.Config.Runner.Connectors["vision"]; ok && vc != nil && vc.Connector != "" {
|
||||
p := resolvePlatform(req.Computer)
|
||||
steps = append(steps, types.PrepareStep{
|
||||
Action: "exec",
|
||||
Cmd: "mkdir -p $HOME/.config/opencode/tools && for f in /opt/opencode-tools/*.ts; do [ -f \"$f\" ] && cp -f \"$f\" $HOME/.config/opencode/tools/; done",
|
||||
Cmd: visionCopyCmd(p),
|
||||
Once: true,
|
||||
IgnoreError: true,
|
||||
})
|
||||
|
|
@ -231,3 +232,15 @@ func (r *Runner) Cleanup(ctx context.Context, computer infra.Computer) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// visionCopyCmd returns the shell command to copy custom OpenCode tools
|
||||
// (e.g. read.ts for vision) from the container image path into the user's
|
||||
// config directory. Platform-aware: bash for POSIX, PowerShell for Windows.
|
||||
func visionCopyCmd(p platform) string {
|
||||
if p.OS() == "windows" {
|
||||
return `$d = Join-Path $env:USERPROFILE '.config\opencode\tools'; ` +
|
||||
`if (!(Test-Path $d)) { New-Item -ItemType Directory -Path $d -Force | Out-Null }; ` +
|
||||
`Copy-Item 'C:\opt\opencode-tools\*.ts' $d -Force -ErrorAction SilentlyContinue`
|
||||
}
|
||||
return `mkdir -p $HOME/.config/opencode/tools && for f in /opt/opencode-tools/*.ts; do [ -f "$f" ] && cp -f "$f" $HOME/.config/opencode/tools/; done`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,72 +103,6 @@ func TestOpenCode_Session(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Scenario 4: No vision connector — read.ts should NOT be copied
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestOpenCode_NoVision_ReadToolNotCopied(t *testing.T) {
|
||||
sandboxtestutils.Prepare(t)
|
||||
defer sandboxtestutils.Clean(t)
|
||||
require.NotNil(t, caller.AgentGetterFunc)
|
||||
|
||||
const assistantID = "tests.sandbox-v2.opencode-oneshot-cli"
|
||||
agent, err := caller.AgentGetterFunc(assistantID)
|
||||
require.NoError(t, err)
|
||||
|
||||
chatID := fmt.Sprintf("e2e-novision-%d", time.Now().UnixMilli())
|
||||
ctx := agentcontext.New(
|
||||
context.Background(),
|
||||
&oauthtypes.AuthorizedInfo{TeamID: "test-team-e2e", UserID: "test-user-e2e"},
|
||||
chatID,
|
||||
)
|
||||
|
||||
resp := streamAndWait(t, agent, ctx,
|
||||
`Check if the file $HOME/.config/opencode/tools/read.ts exists. `+
|
||||
`Reply with exactly "READ_EXISTS" if it does, or "READ_MISSING" if it does not. Nothing else.`,
|
||||
defaultTimeout,
|
||||
)
|
||||
|
||||
require.NotNil(t, resp.Completion)
|
||||
content := strings.ToLower(contentString(t, resp))
|
||||
t.Logf("NoVision check: %s", content)
|
||||
assert.Contains(t, content, "read_missing",
|
||||
"without vision connector, read.ts should NOT be copied")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Scenario 5: With vision connector — read.ts SHOULD be copied
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestOpenCode_Vision_ReadToolCopied(t *testing.T) {
|
||||
sandboxtestutils.Prepare(t)
|
||||
defer sandboxtestutils.Clean(t)
|
||||
require.NotNil(t, caller.AgentGetterFunc)
|
||||
|
||||
const assistantID = "tests.sandbox-v2.opencode-vision-cli"
|
||||
agent, err := caller.AgentGetterFunc(assistantID)
|
||||
require.NoError(t, err)
|
||||
|
||||
chatID := fmt.Sprintf("e2e-vision-%d", time.Now().UnixMilli())
|
||||
ctx := agentcontext.New(
|
||||
context.Background(),
|
||||
&oauthtypes.AuthorizedInfo{TeamID: "test-team-e2e", UserID: "test-user-e2e"},
|
||||
chatID,
|
||||
)
|
||||
|
||||
resp := streamAndWait(t, agent, ctx,
|
||||
`Check if the file $HOME/.config/opencode/tools/read.ts exists. `+
|
||||
`Reply with exactly "READ_EXISTS" if it does, or "READ_MISSING" if it does not. Nothing else.`,
|
||||
defaultTimeout,
|
||||
)
|
||||
|
||||
require.NotNil(t, resp.Completion)
|
||||
content := strings.ToLower(contentString(t, resp))
|
||||
t.Logf("Vision check: %s", content)
|
||||
assert.Contains(t, content, "read_exists",
|
||||
"with vision connector, read.ts SHOULD be copied")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue