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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/opencode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"why-is-node-running": "3.2.2",
"zod-to-json-schema": "3.24.5"
},
"dependencies": {
"dependencies": {
"@actions/core": "1.11.1",
"@actions/github": "6.0.1",
"@agentclientprotocol/sdk": "0.5.1",
Expand Down Expand Up @@ -107,6 +107,7 @@
"open": "10.1.2",
"opentui-spinner": "0.0.6",
"partial-json": "0.1.7",
"playwright": "^1.52.0",
"remeda": "catalog:",
"solid-js": "catalog:",
"strip-ansi": "7.1.2",
Expand Down
41 changes: 41 additions & 0 deletions packages/opencode/src/browser/assertText.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import z from "zod"
import { Tool } from "../tool/tool"
import { BrowserService } from "./index"

export const BrowserAssertTextTool = Tool.define("browser_assertText", {
description: "Assert that an element's text matches an expected value",
parameters: z.object({
selector: z.string().describe("Element selector"),
expected: z.string().describe("Expected text (or pattern)"),
contains: z.boolean().optional().default(false).describe("Should contain vs equal"),
}),
async execute(params, ctx) {
await ctx.ask({
permission: "browser",
patterns: ["*"],
always: ["*"],
metadata: {
action: "assert_text",
selector: params.selector,
expected: params.expected,
},
})

const result = await BrowserService.assertText(params.selector, params.expected, params.contains)

if (!result.passed) {
throw new Error(`Assertion failed: Expected "${params.contains ? 'containing' : 'equal to'}" "${params.expected}", got "${result.actual}"`)
}

return {
title: `Assertion passed`,
output: `✓ Text matches: ${params.expected}`,
metadata: {
selector: params.selector,
expected: params.expected,
actual: result.actual,
passed: true,
},
}
},
})
37 changes: 37 additions & 0 deletions packages/opencode/src/browser/assertURL.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import z from "zod"
import { Tool } from "../tool/tool"
import { BrowserService } from "./index"

export const BrowserAssertURLTool = Tool.define("browser_assertURL", {
description: "Assert that the current URL matches a pattern",
parameters: z.object({
pattern: z.string().describe("URL pattern to match (substring or regex)"),
}),
async execute(params, ctx) {
await ctx.ask({
permission: "browser",
patterns: ["*"],
always: ["*"],
metadata: {
action: "assert_url",
pattern: params.pattern,
},
})

const result = await BrowserService.assertURL(params.pattern)

if (!result.passed) {
throw new Error(`Assertion failed: URL "${result.currentUrl}" does not match "${params.pattern}"`)
}

return {
title: `Assertion passed`,
output: `✓ URL matches: ${params.pattern}`,
metadata: {
pattern: params.pattern,
url: result.url,
passed: true,
},
}
},
})
39 changes: 39 additions & 0 deletions packages/opencode/src/browser/assertVisible.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import z from "zod"
import { Tool } from "../tool/tool"
import { BrowserService } from "./index"

export const BrowserAssertVisibleTool = Tool.define("browser_assertVisible", {
description: "Assert that an element is visible or hidden",
parameters: z.object({
selector: z.string().describe("Element selector"),
visible: z.boolean().optional().default(true).describe("Should be visible or hidden"),
}),
async execute(params, ctx) {
await ctx.ask({
permission: "browser",
patterns: ["*"],
always: ["*"],
metadata: {
action: "assert_visible",
selector: params.selector,
visible: params.visible,
},
})

const result = await BrowserService.assertVisible(params.selector, params.visible)

if (!result.passed) {
throw new Error(`Assertion failed: Element "${params.selector}" should be ${params.visible ? 'visible' : 'hidden'}`)
}

return {
title: `Assertion passed`,
output: `✓ Element is ${params.visible ? 'visible' : 'hidden'}: ${params.selector}`,
metadata: {
selector: params.selector,
visible: params.visible,
passed: true,
},
}
},
})
29 changes: 29 additions & 0 deletions packages/opencode/src/browser/back.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import z from "zod"
import { Tool } from "../tool/tool"
import { BrowserService } from "./index"

export const BrowserBackTool = Tool.define("browser_back", {
description: "Navigate back in browser history",
parameters: z.object({}),
async execute(params, ctx) {
await ctx.ask({
permission: "browser",
patterns: ["*"],
always: ["*"],
metadata: {
action: "back",
},
})

const result = await BrowserService.back()

return {
title: `Went back`,
output: `Navigated back to: ${result.url}`,
metadata: {
url: result.url,
title: result.title,
},
}
},
})
32 changes: 32 additions & 0 deletions packages/opencode/src/browser/browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import z from "zod"
import { Tool } from "../tool/tool"
import { BrowserService } from "./index"

export const BrowserSetTool = Tool.define("browser_set", {
description: "Configure browser settings (chromium, firefox, webkit)",
parameters: z.object({
browser: z.enum(["chromium", "firefox", "webkit"]).describe("Browser to use"),
}),
async execute(params, ctx) {
await ctx.ask({
permission: "browser",
patterns: ["*"],
always: ["*"],
metadata: {
action: "set_browser",
browser: params.browser,
},
})

const config = await BrowserService.getConfig()
config.browser = params.browser

await BrowserService.close()

return {
title: `Browser set to ${params.browser}`,
output: `Browser changed to ${params.browser}. Previous browser was closed.`,
metadata: {},
}
},
})
34 changes: 34 additions & 0 deletions packages/opencode/src/browser/check.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import z from "zod"
import { Tool } from "../tool/tool"
import { BrowserService } from "./index"

export const BrowserCheckTool = Tool.define("browser_check", {
description: "Check or uncheck a checkbox",
parameters: z.object({
selector: z.string().describe("Checkbox selector"),
checked: z.boolean().optional().default(true).describe("Check or uncheck"),
}),
async execute(params, ctx) {
await ctx.ask({
permission: "browser",
patterns: ["*"],
always: ["*"],
metadata: {
action: "check",
selector: params.selector,
checked: params.checked,
},
})

await BrowserService.check(params.selector, params.checked)

return {
title: `${params.checked ? 'Checked' : 'Unchecked'} checkbox`,
output: `${params.checked ? 'Checked' : 'Unchecked'}: ${params.selector}`,
metadata: {
selector: params.selector,
checked: params.checked,
},
}
},
})
31 changes: 31 additions & 0 deletions packages/opencode/src/browser/clear.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import z from "zod"
import { Tool } from "../tool/tool"
import { BrowserService } from "./index"

export const BrowserClearTool = Tool.define("browser_clear", {
description: "Clear an input field",
parameters: z.object({
selector: z.string().describe("Input element selector"),
}),
async execute(params, ctx) {
await ctx.ask({
permission: "browser",
patterns: ["*"],
always: ["*"],
metadata: {
action: "clear",
selector: params.selector,
},
})

await BrowserService.clear(params.selector)

return {
title: `Cleared input`,
output: `Cleared: ${params.selector}`,
metadata: {
selector: params.selector,
},
}
},
})
31 changes: 31 additions & 0 deletions packages/opencode/src/browser/clearStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import z from "zod"
import { Tool } from "../tool/tool"
import { BrowserService } from "./index"

export const BrowserClearStorageTool = Tool.define("browser_clearStorage", {
description: "Clear browser storage (localStorage, sessionStorage, or all)",
parameters: z.object({
type: z.enum(["localStorage", "sessionStorage", "all"]).optional().default("all").describe("Storage type to clear"),
}),
async execute(params, ctx) {
await ctx.ask({
permission: "browser",
patterns: ["*"],
always: ["*"],
metadata: {
action: "clear_storage",
type: params.type,
},
})

await BrowserService.clearStorage(params.type)

return {
title: `Cleared ${params.type}`,
output: `Cleared ${params.type === 'all' ? 'all storage' : params.type}`,
metadata: {
type: params.type,
},
}
},
})
32 changes: 32 additions & 0 deletions packages/opencode/src/browser/click.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import z from "zod"
import { Tool } from "../tool/tool"
import { BrowserService } from "./index"

export const BrowserClickTool = Tool.define("browser_click", {
description: "Click on an element by selector",
parameters: z.object({
selector: z.string().describe("Selector to identify the element to click"),
timeout: z.number().optional().describe("Timeout in milliseconds (default: 10000)"),
}),
async execute(params, ctx) {
await ctx.ask({
permission: "browser",
patterns: ["*"],
always: ["*"],
metadata: {
selector: params.selector,
action: "click",
},
})

const result = await BrowserService.click(params.selector, {
elementTimeoutMs: params.timeout,
})

return {
title: `Clicked element on ${result.url}`,
output: `Successfully clicked element\nPage URL: ${result.url}\nPage title: ${result.title}`,
metadata: {},
}
},
})
26 changes: 26 additions & 0 deletions packages/opencode/src/browser/close.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import z from "zod"
import { Tool } from "../tool/tool"
import { BrowserService } from "./index"

export const BrowserCloseTool = Tool.define("browser_close", {
description: "Close the browser and all tabs",
parameters: z.object({}),
async execute(params, ctx) {
await ctx.ask({
permission: "browser",
patterns: ["*"],
always: ["*"],
metadata: {
action: "close",
},
})

const result = await BrowserService.close()

return {
title: "Browser closed",
output: "Browser closed successfully",
metadata: {},
}
},
})
32 changes: 32 additions & 0 deletions packages/opencode/src/browser/closeTab.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import z from "zod"
import { Tool } from "../tool/tool"
import { BrowserService } from "./index"

export const BrowserCloseTabTool = Tool.define("browser_closeTab", {
description: "Close a tab by index, or the current tab if no index specified",
parameters: z.object({
index: z.number().optional().describe("Tab index to close (default: current tab)"),
}),
async execute(params, ctx) {
await ctx.ask({
permission: "browser",
patterns: ["*"],
always: ["*"],
metadata: {
action: "close_tab",
index: params.index,
},
})

const result = await BrowserService.closeTab(params.index)

return {
title: `Closed tab ${params.index ?? 'current'}`,
output: `Closed tab at index ${params.index ?? 'current'}\nRemaining tabs: ${result.remaining}`,
metadata: {
remaining: result.remaining,
closedIndex: params.index,
},
}
},
})
Loading