Compare commits

..

2 Commits

Author SHA1 Message Date
Frank
dadddc9c8c zen: deprecate gemini 3 pro 2026-03-17 12:31:54 -04:00
OpeOginni
6708c3f6cf docs: mark tools config as deprecated (#17951) 2026-03-17 10:07:35 -05:00
6 changed files with 98 additions and 158 deletions

View File

@@ -19,42 +19,3 @@ test("server picker dialog opens from home", async ({ page }) => {
await expect(dialog).toBeVisible()
await expect(dialog.getByRole("textbox").first()).toBeVisible()
})
test("home hides desktop history and sidebar controls", async ({ page }) => {
await page.setViewportSize({ width: 1400, height: 900 })
await page.goto("/")
await expect(page.getByRole("button", { name: "Toggle sidebar" })).toHaveCount(0)
await expect(page.getByRole("button", { name: "Go back" })).toHaveCount(0)
await expect(page.getByRole("button", { name: "Go forward" })).toHaveCount(0)
await expect(page.getByRole("button", { name: "Toggle menu" })).toHaveCount(0)
})
test("home keeps the mobile menu available", async ({ page }) => {
await page.setViewportSize({ width: 430, height: 900 })
await page.goto("/")
const toggle = page.getByRole("button", { name: "Toggle menu" }).first()
await expect(toggle).toBeVisible()
await toggle.click()
const nav = page.locator('[data-component="sidebar-nav-mobile"]')
await expect(nav).toBeVisible()
await expect.poll(async () => (await nav.boundingBox())?.width ?? 0).toBeLessThan(120)
await expect(nav.getByRole("button", { name: "Settings" })).toBeVisible()
await expect(nav.getByRole("button", { name: "Help" })).toBeVisible()
await page.setViewportSize({ width: 1400, height: 900 })
await expect(nav).toBeHidden()
await page.setViewportSize({ width: 430, height: 900 })
await expect(toggle).toBeVisible()
await expect(toggle).toHaveAttribute("aria-expanded", "false")
await expect(nav).toHaveClass(/-translate-x-full/)
await toggle.click()
await expect(nav).toBeVisible()
await nav.getByRole("button", { name: "Settings" }).click()
await expect(page.getByRole("dialog")).toBeVisible()
})

View File

@@ -58,7 +58,6 @@ export function Titlebar() {
})
const path = () => `${location.pathname}${location.search}${location.hash}`
const home = createMemo(() => !params.dir)
const creating = createMemo(() => {
if (!params.dir) return false
if (params.id) return false
@@ -199,93 +198,91 @@ export function Titlebar() {
/>
</div>
</Show>
<Show when={!home()}>
<div class="flex items-center gap-1 shrink-0">
<TooltipKeybind
class={web() ? "hidden xl:flex shrink-0 ml-14" : "hidden xl:flex shrink-0 ml-2"}
placement="bottom"
title={language.t("command.sidebar.toggle")}
keybind={command.keybind("sidebar.toggle")}
<div class="flex items-center gap-1 shrink-0">
<TooltipKeybind
class={web() ? "hidden xl:flex shrink-0 ml-14" : "hidden xl:flex shrink-0 ml-2"}
placement="bottom"
title={language.t("command.sidebar.toggle")}
keybind={command.keybind("sidebar.toggle")}
>
<Button
variant="ghost"
class="group/sidebar-toggle titlebar-icon w-8 h-6 p-0 box-border"
onClick={layout.sidebar.toggle}
aria-label={language.t("command.sidebar.toggle")}
aria-expanded={layout.sidebar.opened()}
>
<Button
variant="ghost"
class="group/sidebar-toggle titlebar-icon w-8 h-6 p-0 box-border"
onClick={layout.sidebar.toggle}
aria-label={language.t("command.sidebar.toggle")}
aria-expanded={layout.sidebar.opened()}
>
<Icon size="small" name={layout.sidebar.opened() ? "sidebar-active" : "sidebar"} />
</Button>
</TooltipKeybind>
<div class="hidden xl:flex items-center shrink-0">
<Show when={params.dir}>
<div
class="flex items-center shrink-0 w-8 mr-1"
aria-hidden={layout.sidebar.opened() ? "true" : undefined}
>
<div
class="transition-opacity"
classList={{
"opacity-100 duration-120 ease-out": !layout.sidebar.opened(),
"opacity-0 duration-120 ease-in delay-0 pointer-events-none": layout.sidebar.opened(),
}}
>
<TooltipKeybind
placement="bottom"
title={language.t("command.session.new")}
keybind={command.keybind("session.new")}
openDelay={2000}
>
<Button
variant="ghost"
icon={creating() ? "new-session-active" : "new-session"}
class="titlebar-icon w-8 h-6 p-0 box-border"
disabled={layout.sidebar.opened()}
tabIndex={layout.sidebar.opened() ? -1 : undefined}
onClick={() => {
if (!params.dir) return
navigate(`/${params.dir}/session`)
}}
aria-label={language.t("command.session.new")}
aria-current={creating() ? "page" : undefined}
/>
</TooltipKeybind>
</div>
</div>
</Show>
<Icon size="small" name={layout.sidebar.opened() ? "sidebar-active" : "sidebar"} />
</Button>
</TooltipKeybind>
<div class="hidden xl:flex items-center shrink-0">
<Show when={params.dir}>
<div
class="flex items-center gap-0 transition-transform"
classList={{
"translate-x-0": !layout.sidebar.opened(),
"-translate-x-[36px]": layout.sidebar.opened(),
"duration-180 ease-out": !layout.sidebar.opened(),
"duration-180 ease-in": layout.sidebar.opened(),
}}
class="flex items-center shrink-0 w-8 mr-1"
aria-hidden={layout.sidebar.opened() ? "true" : undefined}
>
<Tooltip placement="bottom" value={language.t("common.goBack")} openDelay={2000}>
<Button
variant="ghost"
icon="chevron-left"
class="titlebar-icon w-6 h-6 p-0 box-border"
disabled={!canBack()}
onClick={back}
aria-label={language.t("common.goBack")}
/>
</Tooltip>
<Tooltip placement="bottom" value={language.t("common.goForward")} openDelay={2000}>
<Button
variant="ghost"
icon="chevron-right"
class="titlebar-icon w-6 h-6 p-0 box-border"
disabled={!canForward()}
onClick={forward}
aria-label={language.t("common.goForward")}
/>
</Tooltip>
<div
class="transition-opacity"
classList={{
"opacity-100 duration-120 ease-out": !layout.sidebar.opened(),
"opacity-0 duration-120 ease-in delay-0 pointer-events-none": layout.sidebar.opened(),
}}
>
<TooltipKeybind
placement="bottom"
title={language.t("command.session.new")}
keybind={command.keybind("session.new")}
openDelay={2000}
>
<Button
variant="ghost"
icon={creating() ? "new-session-active" : "new-session"}
class="titlebar-icon w-8 h-6 p-0 box-border"
disabled={layout.sidebar.opened()}
tabIndex={layout.sidebar.opened() ? -1 : undefined}
onClick={() => {
if (!params.dir) return
navigate(`/${params.dir}/session`)
}}
aria-label={language.t("command.session.new")}
aria-current={creating() ? "page" : undefined}
/>
</TooltipKeybind>
</div>
</div>
</Show>
<div
class="flex items-center gap-0 transition-transform"
classList={{
"translate-x-0": !layout.sidebar.opened(),
"-translate-x-[36px]": layout.sidebar.opened(),
"duration-180 ease-out": !layout.sidebar.opened(),
"duration-180 ease-in": layout.sidebar.opened(),
}}
>
<Tooltip placement="bottom" value={language.t("common.goBack")} openDelay={2000}>
<Button
variant="ghost"
icon="chevron-left"
class="titlebar-icon w-6 h-6 p-0 box-border"
disabled={!canBack()}
onClick={back}
aria-label={language.t("common.goBack")}
/>
</Tooltip>
<Tooltip placement="bottom" value={language.t("common.goForward")} openDelay={2000}>
<Button
variant="ghost"
icon="chevron-right"
class="titlebar-icon w-6 h-6 p-0 box-border"
disabled={!canForward()}
onClick={forward}
aria-label={language.t("common.goForward")}
/>
</Tooltip>
</div>
</div>
</Show>
</div>
<div id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" />
</div>

View File

@@ -200,20 +200,13 @@ export default function Layout(props: ParentProps) {
onMount(() => {
const stop = () => setState("sizing", false)
const sync = () => {
if (!window.matchMedia("(min-width: 1280px)").matches) return
layout.mobileSidebar.hide()
}
window.addEventListener("pointerup", stop)
window.addEventListener("pointercancel", stop)
window.addEventListener("blur", stop)
window.addEventListener("resize", sync)
sync()
onCleanup(() => {
window.removeEventListener("pointerup", stop)
window.removeEventListener("pointercancel", stop)
window.removeEventListener("blur", stop)
window.removeEventListener("resize", sync)
})
})
@@ -2249,7 +2242,6 @@ export default function Layout(props: ParentProps) {
<SidebarContent
mobile={mobile}
opened={() => layout.sidebar.opened()}
hasPanel={() => !!currentProject()}
aimMove={aim.move}
projects={projects}
renderProject={(project) => (
@@ -2348,9 +2340,7 @@ export default function Layout(props: ParentProps) {
aria-label={language.t("sidebar.nav.projectsAndSessions")}
data-component="sidebar-nav-mobile"
classList={{
"@container fixed top-10 bottom-0 left-0 z-50 overflow-hidden border-r border-border-weaker-base bg-background-base transition-transform duration-200 ease-out": true,
"w-16": !currentProject(),
"w-full max-w-[400px]": !!currentProject(),
"@container fixed top-10 bottom-0 left-0 z-50 w-full max-w-[400px] overflow-hidden border-r border-border-weaker-base bg-background-base transition-transform duration-200 ease-out": true,
"translate-x-0": layout.mobileSidebar.opened(),
"-translate-x-full": !layout.mobileSidebar.opened(),
}}

View File

@@ -15,7 +15,6 @@ import { type LocalProject } from "@/context/layout"
export const SidebarContent = (props: {
mobile?: boolean
opened: Accessor<boolean>
hasPanel?: Accessor<boolean>
aimMove: (event: MouseEvent) => void
projects: Accessor<LocalProject[]>
renderProject: (project: LocalProject) => JSX.Element
@@ -34,7 +33,6 @@ export const SidebarContent = (props: {
renderPanel: () => JSX.Element
}): JSX.Element => {
const expanded = createMemo(() => !!props.mobile || props.opened())
const hasPanel = createMemo(() => props.hasPanel?.() ?? true)
const placement = () => (props.mobile ? "bottom" : "right")
let panel: HTMLDivElement | undefined
@@ -113,17 +111,15 @@ export const SidebarContent = (props: {
</div>
</div>
<Show when={hasPanel()}>
<div
ref={(el) => {
panel = el
}}
classList={{ "flex-1 flex h-full min-h-0 min-w-0 overflow-hidden": true, "pointer-events-none": !expanded() }}
aria-hidden={!expanded()}
>
{props.renderPanel()}
</div>
</Show>
<div
ref={(el) => {
panel = el
}}
classList={{ "flex-1 flex h-full min-h-0 min-w-0 overflow-hidden": true, "pointer-events-none": !expanded() }}
aria-hidden={!expanded()}
>
{props.renderPanel()}
</div>
</div>
)
}

View File

@@ -366,9 +366,11 @@ The model ID in your OpenCode config uses the format `provider/model-id`. For ex
---
### Tools
### Tools (deprecated)
Control which tools are available in this agent with the `tools` config. You can enable or disable specific tools by setting them to `true` or `false`.
`tools` is **deprecated**. Prefer the agent's [`permission`](#permissions) field for new configs, updates and more fine-grained control.
Allows you to control which tools are available in this agent. You can enable or disable specific tools by setting them to `true` or `false`. In an agent's `tools` config, `true` is equivalent to `{"*": "allow"}` permission and `false` is equivalent to `{"*": "deny"}` permission.
```json title="opencode.json" {3-6,9-12}
{
@@ -392,7 +394,7 @@ Control which tools are available in this agent with the `tools` config. You can
The agent-specific config overrides the global config.
:::
You can also use wildcards to control multiple tools at once. For example, to disable all tools from an MCP server:
You can also use wildcards in legacy `tools` entries to control multiple tools at once. For example, to disable all tools from an MCP server:
```json title="opencode.json"
{

View File

@@ -86,7 +86,6 @@ You can also access our models through the following API endpoints.
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
| Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` |
| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` |
| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 Free | minimax-m2.5-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
@@ -95,8 +94,6 @@ You can also access our models through the following API endpoints.
| GLM 4.7 | glm-4.7 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Kimi K2 Thinking | kimi-k2-thinking | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Kimi K2 | kimi-k2 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Qwen3 Coder 480B | qwen3-coder | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiMo V2 Flash Free | mimo-v2-flash-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
@@ -134,8 +131,6 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**.
| GLM 4.7 | $0.60 | $2.20 | $0.10 | - |
| GLM 4.6 | $0.60 | $2.20 | $0.10 | - |
| Kimi K2.5 | $0.60 | $3.00 | $0.10 | - |
| Kimi K2 Thinking | $0.40 | $2.50 | - | - |
| Kimi K2 | $0.40 | $2.50 | - | - |
| Qwen3 Coder 480B | $0.45 | $1.50 | - | - |
| Claude Opus 4.6 | $5.00 | $25.00 | $0.50 | $6.25 |
| Claude Opus 4.5 | $5.00 | $25.00 | $0.50 | $6.25 |
@@ -149,8 +144,6 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**.
| Claude Haiku 3.5 | $0.80 | $4.00 | $0.08 | $1.00 |
| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
| Gemini 3.1 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - |
| Gemini 3 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
| Gemini 3 Pro (> 200K tokens) | $4.00 | $18.00 | $0.40 | - |
| Gemini 3 Flash | $0.50 | $3.00 | $0.05 | - |
| GPT 5.4 Pro | $30.00 | $180.00 | $30.00 | - |
| GPT 5.4 | $2.50 | $15.00 | $0.25 | - |
@@ -206,12 +199,13 @@ charging you more than $20 if your balance goes below $5.
| Model | Deprecation date |
| ---------------- | ---------------- |
| Qwen3 Coder 480B | Feb 6, 2026 |
| Kimi K2 Thinking | March 6, 2026 |
| Kimi K2 | March 6, 2026 |
| MiniMax M2.1 | March 15, 2026 |
| GLM 4.7 | March 15, 2026 |
| GLM 4.6 | March 15, 2026 |
| Gemini 3 Pro | March 9, 2026 |
| Kimi K2 Thinking | March 6, 2026 |
| Kimi K2 | March 6, 2026 |
| Qwen3 Coder 480B | Feb 6, 2026 |
---