Compare commits

..
Author SHA1 Message Date
Dax Raad 8056842baf sync 2026-02-12 18:19:14 -05:00
Dax Raad 8ea1df1560 fix(file): honor dirs=false in route 2026-02-10 23:18:32 -05:00
Dax Raad 52ae62d4cc feat(file): simplify search type param 2026-02-10 23:16:26 -05:00
Dax Raad 3c11eda350 feat(file): use FFF for file searches 2026-02-10 23:03:26 -05:00
opencode-agent[bot] c6ec2f47ef chore: generate 2026-02-11 01:56:08 +00:00
0fd6f365be fix(core): ensure compaction is more reliable, add reserve token buffer to ensure that input window has enough room to compact (#12924)
Co-authored-by: James Lal <james@littlebearlabs.io>
2026-02-10 19:55:22 -06:00
Aiden ClineandGitHub 60bdb6e9ba tweak: /review prompt to look for behavior changes more explicitly (#13049) 2026-02-10 19:44:42 -06:00
DaxandGitHub 6e9cd576ea fix(tui): default session sidebar to auto (#13046) 2026-02-11 01:26:01 +00:00
OpeOginniandGitHub 53ec15a56a fix(tui): improve amazon-bedrock check to include container credentials (#13037) 2026-02-10 18:51:49 -06:00
github-actions[bot] a90b62267f Update VOUCHED list
https://github.com/anomalyco/opencode/issues/12841#issuecomment-3881500535
2026-02-11 00:37:36 +00:00
Frank 24556331c8 wip: zen 2026-02-10 17:56:10 -05:00
Frank 39145b99e8 wip: zen 2026-02-10 17:44:00 -05:00
Frank 0afa6e03a8 wip: zen 2026-02-10 17:36:32 -05:00
Frank 7a3c775dc1 wip: zen 2026-02-10 17:24:03 -05:00
Frank 3ea58bb790 wip: zen 2026-02-10 17:15:01 -05:00
Adamandopencode 50c705cd2d fix(docs): locale translations 2026-02-10 22:12:32 +00:00
Frankandopencode 3894c217cc wip: zen 2026-02-10 22:12:32 +00:00
Adamandopencode 66c2bb8f37 chore: update website stats 2026-02-10 22:12:32 +00:00
opencode 1bbbd51d48 release: v1.1.56 2026-02-10 22:12:25 +00:00
Adam 50f3e74d05 fix(app): task tool rendering 2026-02-10 15:28:46 -06:00
Adam 21475a1dfd fix(docs): invalid markdown 2026-02-10 15:18:57 -06:00
FilipandGitHub dce4c05fa9 fix(desktop): open apps with executables on Windows (#13022) 2026-02-10 15:10:58 -06:00
Frank 8c56571ef9 zen: log error 2026-02-10 15:55:33 -05:00
AdamandGitHub 92a77b72fb fix(app): don't close sidebar on session change (#13013) 2026-02-10 14:45:52 -06:00
175 changed files with 1666 additions and 1096 deletions
+1
View File
@@ -15,4 +15,5 @@ kitlangton
kommander
r44vc0rp
rekram1-node
-spider-yamet clawdbot/llm psychosis, spam pinging the team
thdxr
+42 -15
View File
@@ -23,7 +23,7 @@
},
"packages/app": {
"name": "@opencode-ai/app",
"version": "1.1.55",
"version": "1.1.56",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -73,7 +73,7 @@
},
"packages/console/app": {
"name": "@opencode-ai/console-app",
"version": "1.1.55",
"version": "1.1.56",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@@ -107,7 +107,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
"version": "1.1.55",
"version": "1.1.56",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -134,7 +134,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
"version": "1.1.55",
"version": "1.1.56",
"dependencies": {
"@ai-sdk/anthropic": "2.0.0",
"@ai-sdk/openai": "2.0.2",
@@ -158,7 +158,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
"version": "1.1.55",
"version": "1.1.56",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -182,7 +182,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
"version": "1.1.55",
"version": "1.1.56",
"dependencies": {
"@opencode-ai/app": "workspace:*",
"@opencode-ai/ui": "workspace:*",
@@ -215,7 +215,7 @@
},
"packages/enterprise": {
"name": "@opencode-ai/enterprise",
"version": "1.1.55",
"version": "1.1.56",
"dependencies": {
"@opencode-ai/ui": "workspace:*",
"@opencode-ai/util": "workspace:*",
@@ -244,7 +244,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
"version": "1.1.55",
"version": "1.1.56",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "catalog:",
@@ -260,7 +260,7 @@
},
"packages/opencode": {
"name": "opencode",
"version": "1.1.55",
"version": "1.1.56",
"bin": {
"opencode": "./bin/opencode",
},
@@ -288,6 +288,7 @@
"@ai-sdk/vercel": "1.0.33",
"@ai-sdk/xai": "2.0.51",
"@clack/prompts": "1.0.0-alpha.1",
"@ff-labs/bun": "0.1.37",
"@gitlab/gitlab-ai-provider": "3.5.0",
"@gitlab/opencode-gitlab-auth": "1.3.2",
"@hono/standard-validator": "0.1.5",
@@ -366,7 +367,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
"version": "1.1.55",
"version": "1.1.56",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"zod": "catalog:",
@@ -386,7 +387,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
"version": "1.1.55",
"version": "1.1.56",
"devDependencies": {
"@hey-api/openapi-ts": "0.90.10",
"@tsconfig/node22": "catalog:",
@@ -397,7 +398,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
"version": "1.1.55",
"version": "1.1.56",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@@ -410,7 +411,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
"version": "1.1.55",
"version": "1.1.56",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -452,7 +453,7 @@
},
"packages/util": {
"name": "@opencode-ai/util",
"version": "1.1.55",
"version": "1.1.56",
"dependencies": {
"zod": "catalog:",
},
@@ -463,7 +464,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
"version": "1.1.55",
"version": "1.1.56",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
@@ -961,6 +962,8 @@
"@fastify/rate-limit": ["@fastify/rate-limit@10.3.0", "", { "dependencies": { "@lukeed/ms": "^2.0.2", "fastify-plugin": "^5.0.0", "toad-cache": "^3.7.0" } }, "sha512-eIGkG9XKQs0nyynatApA3EVrojHOuq4l6fhB4eeCk4PIOeadvOJz9/4w3vGI44Go17uaXOWEcPkaD8kuKm7g6Q=="],
"@ff-labs/bun": ["@ff-labs/bun@0.1.37", "", { "peerDependencies": { "bun": ">=1.0.0" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "fff": "scripts/cli.ts", "fff-demo": "examples/search.ts" } }, "sha512-kDdRSLXCKgZjqVQoVAy2SaXAtAKUsg1GXgYGYTQYGbLcqS1j8oBRA5abnDikamgFbH4QHMC/ml1fQTXUN94TUQ=="],
"@floating-ui/core": ["@floating-ui/core@1.7.4", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg=="],
"@floating-ui/dom": ["@floating-ui/dom@1.7.5", "", { "dependencies": { "@floating-ui/core": "^1.7.4", "@floating-ui/utils": "^0.2.10" } }, "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg=="],
@@ -1305,6 +1308,28 @@
"@oslojs/jwt": ["@oslojs/jwt@0.2.0", "", { "dependencies": { "@oslojs/encoding": "0.4.1" } }, "sha512-bLE7BtHrURedCn4Mco3ma9L4Y1GR2SMBuIvjWr7rmQ4/W/4Jy70TIAgZ+0nIlk0xHz1vNP8x8DCns45Sb2XRbg=="],
"@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.3.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-df7smckMWSUfaT5mzwN9Lfpd3ZGkOqo+vmQ8VV2a32gl14v6uZ/qeeo+1RlANXn8M0uzXPWWCkrKZIWSZUR0qw=="],
"@oven/bun-darwin-x64": ["@oven/bun-darwin-x64@1.3.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-YiLxfsPzQqaVvT2a+nxH9do0YfUjrlxF3tKP0b1DDgvfgCcVKGsrQH3Wa82qHgL4dnT8h2bqi94JxXESEuPmcA=="],
"@oven/bun-darwin-x64-baseline": ["@oven/bun-darwin-x64-baseline@1.3.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-XbhsA2XAFzvFr0vPSV6SNqGxab4xHKdPmVTLqoSHAx9tffrSq/012BDptOskulwnD+YNsrJUx2D2Ve1xvfgGcg=="],
"@oven/bun-linux-aarch64": ["@oven/bun-linux-aarch64@1.3.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-VaNQTu0Up4gnwZLQ6/Hmho6jAlLxTQ1PwxEth8EsXHf82FOXXPV5OCQ6KC9mmmocjKlmWFaIGebThrOy8DUo4g=="],
"@oven/bun-linux-aarch64-musl": ["@oven/bun-linux-aarch64-musl@1.3.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-t8uimCVBTw5f9K2QTZE5wN6UOrFETNrh/Xr7qtXT9nAOzaOnIFvYA+HcHbGfi31fRlCVfTxqm/EiCwJ1gEw9YQ=="],
"@oven/bun-linux-x64": ["@oven/bun-linux-x64@1.3.9", "", { "os": "linux", "cpu": "x64" }, "sha512-oQyAW3+ugulvXTZ+XYeUMmNPR94sJeMokfHQoKwPvVwhVkgRuMhcLGV2ZesHCADVu30Oz2MFXbgdC8x4/o9dRg=="],
"@oven/bun-linux-x64-baseline": ["@oven/bun-linux-x64-baseline@1.3.9", "", { "os": "linux", "cpu": "x64" }, "sha512-nZ12g22cy7pEOBwAxz2tp0wVqekaCn9QRKuGTHqOdLlyAqR4SCdErDvDhUWd51bIyHTQoCmj72TegGTgG0WNPw=="],
"@oven/bun-linux-x64-musl": ["@oven/bun-linux-x64-musl@1.3.9", "", { "os": "linux", "cpu": "x64" }, "sha512-4ZjIUgCxEyKwcKXideB5sX0KJpnHTZtu778w73VNq2uNH2fNpMZv98+DBgJyQ9OfFoRhmKn1bmLmSefvnHzI9w=="],
"@oven/bun-linux-x64-musl-baseline": ["@oven/bun-linux-x64-musl-baseline@1.3.9", "", { "os": "linux", "cpu": "x64" }, "sha512-3FXQgtYFsT0YOmAdMcJn56pLM5kzSl6y942rJJIl5l2KummB9Ea3J/vMJMzQk7NCAGhleZGWU/pJSS/uXKGa7w=="],
"@oven/bun-windows-x64": ["@oven/bun-windows-x64@1.3.9", "", { "os": "win32", "cpu": "x64" }, "sha512-/d6vAmgKvkoYlsGPsRPlPmOK1slPis/F40UG02pYwypTH0wmY0smgzdFqR4YmryxFh17XrW1kITv+U99Oajk9Q=="],
"@oven/bun-windows-x64-baseline": ["@oven/bun-windows-x64-baseline@1.3.9", "", { "os": "win32", "cpu": "x64" }, "sha512-a/+hSrrDpMD7THyXvE2KJy1skxzAD0cnW4K1WjuI/91VqsphjNzvf5t/ZgxEVL4wb6f+hKrSJ5J3aH47zPr61g=="],
"@oxc-minify/binding-android-arm64": ["@oxc-minify/binding-android-arm64@0.96.0", "", { "os": "android", "cpu": "arm64" }, "sha512-lzeIEMu/v6Y+La5JSesq4hvyKtKBq84cgQpKYTYM/yGuNk2tfd5Ha31hnC+mTh48lp/5vZH+WBfjVUjjINCfug=="],
"@oxc-minify/binding-darwin-arm64": ["@oxc-minify/binding-darwin-arm64@0.96.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-i0LkJAUXb4BeBFrJQbMKQPoxf8+cFEffDyLSb7NEzzKuPcH8qrVsnEItoOzeAdYam8Sr6qCHVwmBNEQzl7PWpw=="],
@@ -2177,6 +2202,8 @@
"buffers": ["buffers@0.1.1", "", {}, "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ=="],
"bun": ["bun@1.3.9", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.3.9", "@oven/bun-darwin-x64": "1.3.9", "@oven/bun-darwin-x64-baseline": "1.3.9", "@oven/bun-linux-aarch64": "1.3.9", "@oven/bun-linux-aarch64-musl": "1.3.9", "@oven/bun-linux-x64": "1.3.9", "@oven/bun-linux-x64-baseline": "1.3.9", "@oven/bun-linux-x64-musl": "1.3.9", "@oven/bun-linux-x64-musl-baseline": "1.3.9", "@oven/bun-windows-x64": "1.3.9", "@oven/bun-windows-x64-baseline": "1.3.9" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-v5hkh1us7sMNjfimWE70flYbD5I1/qWQaqmJ45q2qk5H/7muQVa478LSVRSFyGTBUBog2LsPQnfIRdjyWJRY+A=="],
"bun-ffi-structs": ["bun-ffi-structs@0.1.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w=="],
"bun-pty": ["bun-pty@0.4.8", "", {}, "sha512-rO70Mrbr13+jxHHHu2YBkk2pNqrJE5cJn29WE++PUr+GFA0hq/VgtQPZANJ8dJo6d7XImvBk37Innt8GM7O28w=="],
+5 -9
View File
@@ -166,14 +166,10 @@ const bucketNew = new sst.cloudflare.Bucket("ZenDataNew")
const AWS_SES_ACCESS_KEY_ID = new sst.Secret("AWS_SES_ACCESS_KEY_ID")
const AWS_SES_SECRET_ACCESS_KEY = new sst.Secret("AWS_SES_SECRET_ACCESS_KEY")
let logProcessor
if ($app.stage === "production" || $app.stage === "frank") {
const HONEYCOMB_API_KEY = new sst.Secret("HONEYCOMB_API_KEY")
logProcessor = new sst.cloudflare.Worker("LogProcessor", {
handler: "packages/console/function/src/log-processor.ts",
link: [HONEYCOMB_API_KEY],
})
}
const logProcessor = new sst.cloudflare.Worker("LogProcessor", {
handler: "packages/console/function/src/log-processor.ts",
link: [new sst.Secret("HONEYCOMB_API_KEY")],
})
new sst.cloudflare.x.SolidStart("Console", {
domain,
@@ -211,7 +207,7 @@ new sst.cloudflare.x.SolidStart("Console", {
transform: {
worker: {
placement: { mode: "smart" },
tailConsumers: logProcessor ? [{ service: logProcessor.nodes.worker.scriptName }] : [],
tailConsumers: [{ service: logProcessor.nodes.worker.scriptName }],
},
},
},
@@ -0,0 +1,36 @@
import { test, expect } from "../fixtures"
import { closeSidebar, hoverSessionItem } from "../actions"
import { projectSwitchSelector, sessionItemSelector } from "../selectors"
test("collapsed sidebar popover stays open when archiving a session", async ({ page, slug, sdk, gotoSession }) => {
const stamp = Date.now()
const one = await sdk.session.create({ title: `e2e sidebar popover archive 1 ${stamp}` }).then((r) => r.data)
const two = await sdk.session.create({ title: `e2e sidebar popover archive 2 ${stamp}` }).then((r) => r.data)
if (!one?.id) throw new Error("Session create did not return an id")
if (!two?.id) throw new Error("Session create did not return an id")
try {
await gotoSession(one.id)
await closeSidebar(page)
const project = page.locator(projectSwitchSelector(slug)).first()
await expect(project).toBeVisible()
await project.hover()
await expect(page.locator(sessionItemSelector(one.id)).first()).toBeVisible()
await expect(page.locator(sessionItemSelector(two.id)).first()).toBeVisible()
const item = await hoverSessionItem(page, one.id)
await item
.getByRole("button", { name: /archive/i })
.first()
.click()
await expect(page.locator(sessionItemSelector(two.id)).first()).toBeVisible()
} finally {
await sdk.session.delete({ sessionID: one.id }).catch(() => undefined)
await sdk.session.delete({ sessionID: two.id }).catch(() => undefined)
}
})
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/app",
"version": "1.1.55",
"version": "1.1.56",
"description": "",
"type": "module",
"exports": {
@@ -166,6 +166,7 @@ export function SessionHeader() {
})
const [prefs, setPrefs] = persisted(Persist.global("open.app"), createStore({ app: "finder" as OpenApp }))
const [menu, setMenu] = createStore({ open: false })
const canOpen = createMemo(() => platform.platform === "desktop" && !!platform.openPath && server.isLocal())
const current = createMemo(() => options().find((o) => o.id === prefs.app) ?? options()[0])
@@ -355,7 +356,12 @@ export function SessionHeader() {
<span class="text-12-regular text-text-strong">Open</span>
</Button>
<div class="self-stretch w-px bg-border-base/70" />
<DropdownMenu gutter={6} placement="bottom-end">
<DropdownMenu
gutter={6}
placement="bottom-end"
open={menu.open}
onOpenChange={(open) => setMenu("open", open)}
>
<DropdownMenu.Trigger
as={IconButton}
icon="chevron-down"
@@ -375,7 +381,13 @@ export function SessionHeader() {
}}
>
{options().map((o) => (
<DropdownMenu.RadioItem value={o.id} onSelect={() => openDir(o.id)}>
<DropdownMenu.RadioItem
value={o.id}
onSelect={() => {
setMenu("open", false)
openDir(o.id)
}}
>
<div class="flex size-5 shrink-0 items-center justify-center">
<AppIcon id={o.icon} class={size(o.icon)} />
</div>
@@ -388,7 +400,12 @@ export function SessionHeader() {
</DropdownMenu.RadioGroup>
</DropdownMenu.Group>
<DropdownMenu.Separator />
<DropdownMenu.Item onSelect={copyPath}>
<DropdownMenu.Item
onSelect={() => {
setMenu("open", false)
copyPath()
}}
>
<div class="flex size-5 shrink-0 items-center justify-center">
<Icon name="copy" size="small" class="text-icon-weak" />
</div>
@@ -54,6 +54,13 @@ export default function Layout(props: ParentProps) {
navigate(`/${params.dir}/session/${sessionID}`)
}
const sessionHref = (sessionID: string) => {
if (params.dir) return `/${params.dir}/session/${sessionID}`
return `/session/${sessionID}`
}
const syncSession = (sessionID: string) => sync.session.sync(sessionID)
return (
<DataProvider
data={sync.data}
@@ -62,6 +69,8 @@ export default function Layout(props: ParentProps) {
onQuestionReply={replyToQuestion}
onQuestionReject={rejectQuestion}
onNavigateToSession={navigateToSession}
onSessionHref={sessionHref}
onSyncSession={syncSession}
>
<LocalProvider>{props.children}</LocalProvider>
</DataProvider>
-14
View File
@@ -181,20 +181,6 @@ export default function Layout(props: ParentProps) {
aim.reset()
})
createEffect(
on(
() => ({ dir: params.dir, id: params.id }),
() => {
if (layout.sidebar.opened()) return
if (!state.hoverProject) return
aim.reset()
setState("hoverSession", undefined)
setState("hoverProject", undefined)
},
{ defer: true },
),
)
const autoselecting = createMemo(() => {
if (params.dir) return false
if (!state.autoselect) return false
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-app",
"version": "1.1.55",
"version": "1.1.56",
"type": "module",
"license": "MIT",
"scripts": {
+4 -4
View File
@@ -9,8 +9,8 @@ export const config = {
github: {
repoUrl: "https://github.com/anomalyco/opencode",
starsFormatted: {
compact: "95K",
full: "95,000",
compact: "100K",
full: "100,000",
},
},
@@ -22,8 +22,8 @@ export const config = {
// Static stats (used on landing page)
stats: {
contributors: "650",
commits: "8,500",
contributors: "700",
commits: "9,000",
monthlyUsers: "2.5M",
},
} as const
@@ -3,11 +3,13 @@ export class CreditsError extends Error {}
export class MonthlyLimitError extends Error {}
export class UserLimitError extends Error {}
export class ModelError extends Error {}
export class FreeUsageLimitError extends Error {}
export class SubscriptionUsageLimitError extends Error {
class LimitError extends Error {
retryAfter?: number
constructor(message: string, retryAfter?: number) {
super(message)
this.retryAfter = retryAfter
}
}
export class FreeUsageLimitError extends LimitError {}
export class SubscriptionUsageLimitError extends LimitError {}
@@ -134,20 +134,26 @@ export async function handler(
body: reqBody,
})
// Try another provider => stop retrying if using fallback provider
if (
res.status !== 200 &&
// ie. openai 404 error: Item with id 'msg_0ead8b004a3b165d0069436a6b6834819896da85b63b196a3f' not found.
res.status !== 404 &&
// ie. cannot change codex model providers mid-session
modelInfo.stickyProvider !== "strict" &&
modelInfo.fallbackProvider &&
providerInfo.id !== modelInfo.fallbackProvider
) {
return retriableRequest({
excludeProviders: [...retry.excludeProviders, providerInfo.id],
retryCount: retry.retryCount + 1,
if (res.status !== 200) {
logger.metric({
"llm.error.code": res.status,
"llm.error.message": res.statusText,
})
// Try another provider => stop retrying if using fallback provider
if (
// ie. openai 404 error: Item with id 'msg_0ead8b004a3b165d0069436a6b6834819896da85b63b196a3f' not found.
res.status !== 404 &&
// ie. cannot change codex model providers mid-session
modelInfo.stickyProvider !== "strict" &&
modelInfo.fallbackProvider &&
providerInfo.id !== modelInfo.fallbackProvider
) {
return retriableRequest({
excludeProviders: [...retry.excludeProviders, providerInfo.id],
retryCount: retry.retryCount + 1,
})
}
}
return { providerInfo, reqBody, res, startTimestamp }
@@ -307,7 +313,7 @@ export async function handler(
if (error instanceof FreeUsageLimitError || error instanceof SubscriptionUsageLimitError) {
const headers = new Headers()
if (error instanceof SubscriptionUsageLimitError && error.retryAfter) {
if (error.retryAfter) {
headers.set("retry-after", String(error.retryAfter))
}
return new Response(
@@ -28,17 +28,46 @@ export function createRateLimiter(limit: ZenData.RateLimit | undefined, rawIp: s
check: async () => {
const rows = await Database.use((tx) =>
tx
.select({ count: IpRateLimitTable.count })
.select({ interval: IpRateLimitTable.interval, count: IpRateLimitTable.count })
.from(IpRateLimitTable)
.where(and(eq(IpRateLimitTable.ip, ip), inArray(IpRateLimitTable.interval, intervals))),
)
const total = rows.reduce((sum, r) => sum + r.count, 0)
logger.debug(`rate limit total: ${total}`)
if (total >= limitValue) throw new FreeUsageLimitError(`Rate limit exceeded. Please try again later.`)
if (total >= limitValue)
throw new FreeUsageLimitError(
`Rate limit exceeded. Please try again later.`,
limit.period === "day" ? getRetryAfterDay(now) : getRetryAfterHour(rows, intervals, limitValue, now),
)
},
}
}
export function getRetryAfterDay(now: number) {
return Math.ceil((86_400_000 - (now % 86_400_000)) / 1000)
}
export function getRetryAfterHour(
rows: { interval: string; count: number }[],
intervals: string[],
limit: number,
now: number,
) {
const counts = new Map(rows.map((r) => [r.interval, r.count]))
// intervals are ordered newest to oldest: [current, -1h, -2h]
// simulate dropping oldest intervals one at a time
let running = intervals.reduce((sum, i) => sum + (counts.get(i) ?? 0), 0)
for (let i = intervals.length - 1; i >= 0; i--) {
running -= counts.get(intervals[i]) ?? 0
if (running < limit) {
// interval at index i rolls out of the window (intervals.length - i) hours from the current hour start
const hours = intervals.length - i
return Math.ceil((hours * 3_600_000 - (now % 3_600_000)) / 1000)
}
}
return Math.ceil((3_600_000 - (now % 3_600_000)) / 1000)
}
function buildYYYYMMDD(timestamp: number) {
return new Date(timestamp)
.toISOString()
@@ -0,0 +1,92 @@
import { describe, expect, test } from "bun:test"
import { getRetryAfterDay, getRetryAfterHour } from "../src/routes/zen/util/rateLimiter"
describe("getRetryAfterDay", () => {
test("returns full day at midnight UTC", () => {
const midnight = Date.UTC(2026, 0, 15, 0, 0, 0, 0)
expect(getRetryAfterDay(midnight)).toBe(86_400)
})
test("returns remaining seconds until next UTC day", () => {
const noon = Date.UTC(2026, 0, 15, 12, 0, 0, 0)
expect(getRetryAfterDay(noon)).toBe(43_200)
})
test("rounds up to nearest second", () => {
const almost = Date.UTC(2026, 0, 15, 23, 59, 59, 500)
expect(getRetryAfterDay(almost)).toBe(1)
})
})
describe("getRetryAfterHour", () => {
// 14:30:00 UTC — 30 minutes into the current hour
const now = Date.UTC(2026, 0, 15, 14, 30, 0, 0)
const intervals = ["2026011514", "2026011513", "2026011512"]
test("waits 3 hours when all usage is in current hour", () => {
const rows = [{ interval: "2026011514", count: 10 }]
// only current hour has usage — it won't leave the window for 3 hours from hour start
// 3 * 3600 - 1800 = 9000s
expect(getRetryAfterHour(rows, intervals, 10, now)).toBe(9000)
})
test("waits 1 hour when dropping oldest interval is sufficient", () => {
const rows = [
{ interval: "2026011514", count: 2 },
{ interval: "2026011512", count: 10 },
]
// total=12, drop oldest (-2h, count=10) -> 2 < 10
// hours = 3 - 2 = 1 -> 1 * 3600 - 1800 = 1800s
expect(getRetryAfterHour(rows, intervals, 10, now)).toBe(1800)
})
test("waits 2 hours when usage spans oldest two intervals", () => {
const rows = [
{ interval: "2026011513", count: 8 },
{ interval: "2026011512", count: 5 },
]
// total=13, drop -2h (5) -> 8, 8 >= 8, drop -1h (8) -> 0 < 8
// hours = 3 - 1 = 2 -> 2 * 3600 - 1800 = 5400s
expect(getRetryAfterHour(rows, intervals, 8, now)).toBe(5400)
})
test("waits 1 hour when oldest interval alone pushes over limit", () => {
const rows = [
{ interval: "2026011514", count: 1 },
{ interval: "2026011513", count: 1 },
{ interval: "2026011512", count: 10 },
]
// total=12, drop -2h (10) -> 2 < 10
// hours = 3 - 2 = 1 -> 1800s
expect(getRetryAfterHour(rows, intervals, 10, now)).toBe(1800)
})
test("waits 2 hours when middle interval keeps total over limit", () => {
const rows = [
{ interval: "2026011514", count: 4 },
{ interval: "2026011513", count: 4 },
{ interval: "2026011512", count: 4 },
]
// total=12, drop -2h (4) -> 8, 8 >= 5, drop -1h (4) -> 4 < 5
// hours = 3 - 1 = 2 -> 5400s
expect(getRetryAfterHour(rows, intervals, 5, now)).toBe(5400)
})
test("rounds up to nearest second", () => {
const offset = Date.UTC(2026, 0, 15, 14, 30, 0, 500)
const rows = [
{ interval: "2026011514", count: 2 },
{ interval: "2026011512", count: 10 },
]
// hours=1 -> 3_600_000 - 1_800_500 = 1_799_500ms -> ceil(1799.5) = 1800
expect(getRetryAfterHour(rows, intervals, 10, offset)).toBe(1800)
})
test("fallback returns time until next hour when rows are empty", () => {
// edge case: rows empty but function called (shouldn't happen in practice)
// loop drops all zeros, running stays 0 which is < any positive limit on first iteration
const rows: { interval: string; count: number }[] = []
// drop -2h (0) -> 0 < 1 -> hours = 3 - 2 = 1 -> 1800s
expect(getRetryAfterHour(rows, intervals, 1, now)).toBe(1800)
})
})
+2 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/console-core",
"version": "1.1.55",
"version": "1.1.56",
"private": true,
"type": "module",
"license": "MIT",
@@ -19,6 +19,7 @@
"zod": "catalog:"
},
"exports": {
"./*.js": "./src/*.ts",
"./*": "./src/*"
},
"scripts": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-function",
"version": "1.1.55",
"version": "1.1.56",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+12 -7
View File
@@ -17,8 +17,7 @@ export default {
)
return
let metrics = {
event_type: "completions",
let data = {
"cf.continent": event.event.request.cf?.continent,
"cf.country": event.event.request.cf?.country,
"cf.city": event.event.request.cf?.city,
@@ -31,22 +30,28 @@ export default {
status: event.event.response?.status ?? 0,
ip: event.event.request.headers["x-real-ip"],
}
const time = new Date(event.eventTimestamp ?? Date.now()).toISOString()
const events = []
for (const log of event.logs) {
for (const message of log.message) {
if (!message.startsWith("_metric:")) continue
metrics = { ...metrics, ...JSON.parse(message.slice(8)) }
const json = JSON.parse(message.slice(8))
data = { ...data, ...json }
if ("llm.error.code" in json) {
events.push({ time, data: { ...data, event_type: "llm.error" } })
}
}
}
console.log(JSON.stringify(metrics, null, 2))
events.push({ time, data: { ...data, event_type: "completions" } })
console.log(JSON.stringify(data, null, 2))
const ret = await fetch("https://api.honeycomb.io/1/events/zen", {
const ret = await fetch("https://api.honeycomb.io/1/batch/zen", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Honeycomb-Event-Time": (event.eventTimestamp ?? Date.now()).toString(),
"X-Honeycomb-Team": Resource.HONEYCOMB_API_KEY.value,
},
body: JSON.stringify(metrics),
body: JSON.stringify(events),
})
console.log(ret.status)
console.log(await ret.text())
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-mail",
"version": "1.1.55",
"version": "1.1.56",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/desktop",
"private": true,
"version": "1.1.55",
"version": "1.1.56",
"type": "module",
"license": "MIT",
"scripts": {
+162 -7
View File
@@ -20,9 +20,9 @@ use std::{
env,
net::TcpListener,
path::PathBuf,
process::Command,
sync::{Arc, Mutex},
time::Duration,
process::Command,
};
use tauri::{AppHandle, Manager, RunEvent, State, ipc::Channel};
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
@@ -152,12 +152,12 @@ fn check_app_exists(app_name: &str) -> bool {
{
check_windows_app(app_name)
}
#[cfg(target_os = "macos")]
{
check_macos_app(app_name)
}
#[cfg(target_os = "linux")]
{
check_linux_app(app_name)
@@ -165,11 +165,165 @@ fn check_app_exists(app_name: &str) -> bool {
}
#[cfg(target_os = "windows")]
fn check_windows_app(app_name: &str) -> bool {
fn check_windows_app(_app_name: &str) -> bool {
// Check if command exists in PATH, including .exe
return true;
}
#[cfg(target_os = "windows")]
fn resolve_windows_app_path(app_name: &str) -> Option<String> {
use std::path::{Path, PathBuf};
// Try to find the command using 'where'
let output = Command::new("where").arg(app_name).output().ok()?;
if !output.status.success() {
return None;
}
let paths = String::from_utf8_lossy(&output.stdout)
.lines()
.map(str::trim)
.filter(|line| !line.is_empty())
.map(PathBuf::from)
.collect::<Vec<_>>();
let has_ext = |path: &Path, ext: &str| {
path.extension()
.and_then(|v| v.to_str())
.map(|v| v.eq_ignore_ascii_case(ext))
.unwrap_or(false)
};
if let Some(path) = paths.iter().find(|path| has_ext(path, "exe")) {
return Some(path.to_string_lossy().to_string());
}
let resolve_cmd = |path: &Path| -> Option<String> {
let content = std::fs::read_to_string(path).ok()?;
for token in content.split('"') {
let lower = token.to_ascii_lowercase();
if !lower.contains(".exe") {
continue;
}
if let Some(index) = lower.find("%~dp0") {
let base = path.parent()?;
let suffix = &token[index + 5..];
let mut resolved = PathBuf::from(base);
for part in suffix.replace('/', "\\").split('\\') {
if part.is_empty() || part == "." {
continue;
}
if part == ".." {
let _ = resolved.pop();
continue;
}
resolved.push(part);
}
if resolved.exists() {
return Some(resolved.to_string_lossy().to_string());
}
}
let resolved = PathBuf::from(token);
if resolved.exists() {
return Some(resolved.to_string_lossy().to_string());
}
}
None
};
for path in &paths {
if has_ext(path, "cmd") || has_ext(path, "bat") {
if let Some(resolved) = resolve_cmd(path) {
return Some(resolved);
}
}
if path.extension().is_none() {
let cmd = path.with_extension("cmd");
if cmd.exists() {
if let Some(resolved) = resolve_cmd(&cmd) {
return Some(resolved);
}
}
let bat = path.with_extension("bat");
if bat.exists() {
if let Some(resolved) = resolve_cmd(&bat) {
return Some(resolved);
}
}
}
}
let key = app_name
.chars()
.filter(|v| v.is_ascii_alphanumeric())
.flat_map(|v| v.to_lowercase())
.collect::<String>();
if !key.is_empty() {
for path in &paths {
let dirs = [
path.parent(),
path.parent().and_then(|dir| dir.parent()),
path.parent()
.and_then(|dir| dir.parent())
.and_then(|dir| dir.parent()),
];
for dir in dirs.into_iter().flatten() {
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
let candidate = entry.path();
if !has_ext(&candidate, "exe") {
continue;
}
let Some(stem) = candidate.file_stem().and_then(|v| v.to_str()) else {
continue;
};
let name = stem
.chars()
.filter(|v| v.is_ascii_alphanumeric())
.flat_map(|v| v.to_lowercase())
.collect::<String>();
if name.contains(&key) || key.contains(&name) {
return Some(candidate.to_string_lossy().to_string());
}
}
}
}
}
}
paths.first().map(|path| path.to_string_lossy().to_string())
}
#[tauri::command]
#[specta::specta]
fn resolve_app_path(app_name: &str) -> Option<String> {
#[cfg(target_os = "windows")]
{
resolve_windows_app_path(app_name)
}
#[cfg(not(target_os = "windows"))]
{
// On macOS/Linux, just return the app_name as-is since
// the opener plugin handles them correctly
Some(app_name.to_string())
}
}
#[cfg(target_os = "macos")]
fn check_macos_app(app_name: &str) -> bool {
// Check common installation locations
@@ -181,13 +335,13 @@ fn check_macos_app(app_name: &str) -> bool {
if let Ok(home) = std::env::var("HOME") {
app_locations.push(format!("{}/Applications/{}.app", home, app_name));
}
for location in app_locations {
if std::path::Path::new(&location).exists() {
return true;
}
}
// Also check if command exists in PATH
Command::new("which")
.arg(app_name)
@@ -251,7 +405,8 @@ pub fn run() {
get_display_backend,
set_display_backend,
markdown::parse_markdown_command,
check_app_exists
check_app_exists,
resolve_app_path
])
.events(tauri_specta::collect_events![LoadingWindowComplete])
.error_handling(tauri_specta::ErrorHandlingMode::Throw);
+1
View File
@@ -14,6 +14,7 @@ export const commands = {
setDisplayBackend: (backend: LinuxDisplayBackend) => __TAURI_INVOKE<null>("set_display_backend", { backend }),
parseMarkdownCommand: (markdown: string) => __TAURI_INVOKE<string>("parse_markdown_command", { markdown }),
checkAppExists: (appName: string) => __TAURI_INVOKE<boolean>("check_app_exists", { appName }),
resolveAppPath: (appName: string) => __TAURI_INVOKE<string | null>("resolve_app_path", { appName }),
};
/** Events */
+6 -1
View File
@@ -98,7 +98,12 @@ const createPlatform = (password: Accessor<string | null>): Platform => ({
void shellOpen(url).catch(() => undefined)
},
openPath(path: string, app?: string) {
async openPath(path: string, app?: string) {
const os = ostype()
if (os === "windows" && app) {
const resolvedApp = await commands.resolveAppPath(app)
return openerOpenPath(path, resolvedApp || app)
}
return openerOpenPath(path, app)
},
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/enterprise",
"version": "1.1.55",
"version": "1.1.56",
"private": true,
"type": "module",
"license": "MIT",
+6 -6
View File
@@ -1,7 +1,7 @@
id = "opencode"
name = "OpenCode"
description = "The open source coding agent."
version = "1.1.55"
version = "1.1.56"
schema_version = 1
authors = ["Anomaly"]
repository = "https://github.com/anomalyco/opencode"
@@ -11,26 +11,26 @@ name = "OpenCode"
icon = "./icons/opencode.svg"
[agent_servers.opencode.targets.darwin-aarch64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.55/opencode-darwin-arm64.zip"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.56/opencode-darwin-arm64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.darwin-x86_64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.55/opencode-darwin-x64.zip"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.56/opencode-darwin-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-aarch64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.55/opencode-linux-arm64.tar.gz"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.56/opencode-linux-arm64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-x86_64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.55/opencode-linux-x64.tar.gz"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.56/opencode-linux-x64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.windows-x86_64]
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.55/opencode-windows-x64.zip"
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.56/opencode-windows-x64.zip"
cmd = "./opencode.exe"
args = ["acp"]
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/function",
"version": "1.1.55",
"version": "1.1.56",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+2 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.1.55",
"version": "1.1.56",
"name": "opencode",
"type": "module",
"license": "MIT",
@@ -71,6 +71,7 @@
"@ai-sdk/vercel": "1.0.33",
"@ai-sdk/xai": "2.0.51",
"@clack/prompts": "1.0.0-alpha.1",
"@ff-labs/bun": "0.1.37",
"@gitlab/gitlab-ai-provider": "3.5.0",
"@gitlab/opencode-gitlab-auth": "1.3.2",
"@hono/standard-validator": "0.1.5",
@@ -1,6 +1,6 @@
You are a helpful AI assistant tasked with summarizing conversations.
When asked to summarize, provide a detailed but concise summary of the conversation.
When asked to summarize, provide a detailed but concise summary of the conversation.
Focus on information that would be helpful for continuing the conversation, including:
- What was done
- What is currently being worked on
@@ -10,3 +10,5 @@ Focus on information that would be helpful for continuing the conversation, incl
- Important technical decisions and why they were made
Your summary should be comprehensive enough to provide context but concise enough to be quickly understood.
Do not respond to any questions in the conversation, only output the summary.
@@ -7,27 +7,6 @@ import { useDialog } from "@tui/ui/dialog"
import { createDialogProviderOptions, DialogProvider } from "./dialog-provider"
import { useKeybind } from "../context/keybind"
import * as fuzzysort from "fuzzysort"
import type { Provider } from "@opencode-ai/sdk/v2"
function pickLatest(models: [string, Provider["models"][string]][]) {
const picks: Record<string, [string, Provider["models"][string]]> = {}
for (const item of models) {
const model = item[0]
const info = item[1]
const key = info.family ?? model
const prev = picks[key]
if (!prev) {
picks[key] = item
continue
}
if (info.release_date !== prev[1].release_date) {
if (info.release_date > prev[1].release_date) picks[key] = item
continue
}
if (model > prev[0]) picks[key] = item
}
return Object.values(picks)
}
export function useConnected() {
const sync = useSync()
@@ -42,7 +21,6 @@ export function DialogModel(props: { providerID?: string }) {
const dialog = useDialog()
const keybind = useKeybind()
const [query, setQuery] = createSignal("")
const [all, setAll] = createSignal(false)
const connected = useConnected()
const providers = createDialogProviderOptions()
@@ -94,8 +72,8 @@ export function DialogModel(props: { providerID?: string }) {
(provider) => provider.id !== "opencode",
(provider) => provider.name,
),
flatMap((provider) => {
const items = pipe(
flatMap((provider) =>
pipe(
provider.models,
entries(),
filter(([_, info]) => info.status !== "deprecated"),
@@ -126,9 +104,8 @@ export function DialogModel(props: { providerID?: string }) {
(x) => x.footer !== "Free",
(x) => x.title,
),
)
return items
}),
),
),
)
const popularProviders = !connected()
@@ -177,13 +154,6 @@ export function DialogModel(props: { providerID?: string }) {
local.model.toggleFavorite(option.value as { providerID: string; modelID: string })
},
},
{
keybind: keybind.all.model_show_all_toggle?.[0],
title: all() ? "Show latest only" : "Show all models",
onTrigger: () => {
setAll((value) => !value)
},
},
]}
onFilter={setQuery}
flat={true}
@@ -141,7 +141,7 @@ export function Session() {
})
const dimensions = useTerminalDimensions()
const [sidebar, setSidebar] = kv.signal<"auto" | "hide">("sidebar", "hide")
const [sidebar, setSidebar] = kv.signal<"auto" | "hide">("sidebar", "auto")
const [sidebarOpen, setSidebarOpen] = createSignal(false)
const [conceal, setConceal] = createSignal(true)
const [showThinking, setShowThinking] = kv.signal("thinking_visibility", true)
@@ -57,6 +57,8 @@ Use best judgement when processing input.
**Performance** - Only flag if obviously problematic.
- O(n²) on unbounded data, N+1 queries, blocking I/O on hot paths
**Behavior Changes** - If a behavioral change is introduced, raise it (especially if it's possibly unintentional).
---
## Before You Flag Something
+6 -1
View File
@@ -778,7 +778,6 @@ export namespace Config {
stash_delete: z.string().optional().default("ctrl+d").describe("Delete stash entry"),
model_provider_list: z.string().optional().default("ctrl+a").describe("Open provider list from model dialog"),
model_favorite_toggle: z.string().optional().default("ctrl+f").describe("Toggle model favorite status"),
model_show_all_toggle: z.string().optional().default("ctrl+o").describe("Toggle showing all models"),
session_share: z.string().optional().default("none").describe("Share current session"),
session_unshare: z.string().optional().default("none").describe("Unshare current session"),
session_interrupt: z.string().optional().default("escape").describe("Interrupt current session"),
@@ -1162,6 +1161,12 @@ export namespace Config {
.object({
auto: z.boolean().optional().describe("Enable automatic compaction when context is full (default: true)"),
prune: z.boolean().optional().describe("Enable pruning of old tool outputs (default: true)"),
reserved: z
.number()
.int()
.min(0)
.optional()
.describe("Token buffer for compaction. Leaves enough window to avoid overflow during compaction."),
})
.optional(),
experimental: z
+29
View File
@@ -0,0 +1,29 @@
import { FileFinder } from "@ff-labs/bun"
import { Log } from "@/util/log"
export namespace FFF {
const log = Log.create({ service: "file.fff" })
const init = (cwd: string) => {
const result = FileFinder.init({ basePath: cwd })
if (result.ok) return true
log.error("init failed", { error: result.error, cwd })
return false
}
export async function search(input: { cwd: string; query: string; limit: number }) {
if (!input.query) return []
if (!init(input.cwd)) return []
const result = FileFinder.search(input.query, {
pageIndex: 0,
pageSize: input.limit,
})
if (!result.ok) {
log.error("search failed", { error: result.error, query: input.query, cwd: input.cwd })
return []
}
return result.value.items.map((item: { relativePath: string }) => item.relativePath)
}
}
+45 -25
View File
@@ -9,6 +9,7 @@ import ignore from "ignore"
import { Log } from "../util/log"
import { Filesystem } from "../util/filesystem"
import { Instance } from "../project/instance"
import { FFF } from "./fff"
import { Ripgrep } from "./ripgrep"
import fuzzysort from "fuzzysort"
import { Global } from "../global"
@@ -541,42 +542,61 @@ export namespace File {
})
}
export async function search(input: { query: string; limit?: number; dirs?: boolean; type?: "file" | "directory" }) {
export async function search(input: { query: string; limit?: number; type?: "file" | "directory" | "all" }) {
const query = input.query.trim()
const limit = input.limit ?? 100
const kind = input.type ?? (input.dirs === false ? "file" : "all")
const kind = input.type ?? "all"
log.info("search", { query, kind })
const result = await state().then((x) => x.files())
const hidden = (item: string) => {
const normalized = item.replaceAll("\\", "/").replace(/\/+$/, "")
return normalized.split("/").some((p) => p.startsWith(".") && p.length > 1)
}
const preferHidden = query.startsWith(".") || query.includes("/.")
const sortHiddenLast = (items: string[]) => {
if (preferHidden) return items
const visible: string[] = []
const hiddenItems: string[] = []
for (const item of items) {
const isHidden = hidden(item)
if (isHidden) hiddenItems.push(item)
if (!isHidden) visible.push(item)
const files = await FFF.search({
cwd: Instance.directory,
query,
limit: kind === "all" || kind === "directory" ? limit * 20 : limit,
})
const set = new Set<string>()
for (const file of files) {
let dir = path.dirname(file)
while (true) {
if (dir === ".") break
const next = path.dirname(dir)
set.add(dir + "/")
if (next === dir) break
dir = next
}
return [...visible, ...hiddenItems]
}
const allDirs = Array.from(set)
if (!query) {
if (kind === "file") return result.files.slice(0, limit)
return sortHiddenLast(result.dirs.toSorted()).slice(0, limit)
const output = kind === "file" ? files.slice(0, limit) : allDirs.toSorted().slice(0, limit)
log.info("search", { query, kind, results: output.length })
return output
}
const items =
kind === "file" ? result.files : kind === "directory" ? result.dirs : [...result.files, ...result.dirs]
if (kind === "directory") {
const ranked: string[] = []
for (const item of fuzzysort.go(query, allDirs, { limit: limit * 20 })) {
ranked.push(item.target)
}
const output = ranked.slice(0, limit)
log.info("search", { query, kind, results: output.length })
return output
}
const searchLimit = kind === "directory" && !preferHidden ? limit * 20 : limit
const sorted = fuzzysort.go(query, items, { limit: searchLimit }).map((r) => r.target)
const output = kind === "directory" ? sortHiddenLast(sorted).slice(0, limit) : sorted
if (kind === "file") {
const output = files.slice(0, limit)
log.info("search", { query, kind, results: output.length })
return output
}
const rankedDirs: string[] = []
for (const item of fuzzysort.go(query, allDirs, { limit })) {
rankedDirs.push(item.target)
}
const merged = files.slice(0, limit).concat(rankedDirs)
const output: string[] = []
for (const item of fuzzysort.go(query, merged, { limit })) {
output.push(item.target)
}
log.info("search", { query, kind, results: output.length })
return output
}
+6 -1
View File
@@ -211,7 +211,12 @@ export namespace Provider {
const awsWebIdentityTokenFile = Env.get("AWS_WEB_IDENTITY_TOKEN_FILE")
if (!profile && !awsAccessKeyId && !awsBearerToken && !awsWebIdentityTokenFile) return { autoload: false }
const containerCreds = Boolean(
process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI || process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI,
)
if (!profile && !awsAccessKeyId && !awsBearerToken && !awsWebIdentityTokenFile && !containerCreds)
return { autoload: false }
const providerOptions: AmazonBedrockProviderSettings = {
region: defaultRegion,
+5 -23
View File
@@ -5,6 +5,7 @@ import type { JSONSchema } from "zod/v4/core"
import type { Provider } from "./provider"
import type { ModelsDev } from "./models"
import { iife } from "@/util/iife"
import { Flag } from "@/flag/flag"
type Modality = NonNullable<ModelsDev.Model["modalities"]>["input"][number]
@@ -17,6 +18,8 @@ function mimeToModality(mime: string): Modality | undefined {
}
export namespace ProviderTransform {
export const OUTPUT_TOKEN_MAX = Flag.OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX || 32_000
// Maps npm package to the key the AI SDK expects for providerOptions
function sdkKey(npm: string): string | undefined {
switch (npm) {
@@ -723,29 +726,8 @@ export namespace ProviderTransform {
return { [key]: options }
}
export function maxOutputTokens(
npm: string,
options: Record<string, any>,
modelLimit: number,
globalLimit: number,
): number {
const modelCap = modelLimit || globalLimit
const standardLimit = Math.min(modelCap, globalLimit)
if (npm === "@ai-sdk/anthropic" || npm === "@ai-sdk/google-vertex/anthropic") {
const thinking = options?.["thinking"]
const budgetTokens = typeof thinking?.["budgetTokens"] === "number" ? thinking["budgetTokens"] : 0
const enabled = thinking?.["type"] === "enabled"
if (enabled && budgetTokens > 0) {
// Return text tokens so that text + thinking <= model cap, preferring 32k text when possible.
if (budgetTokens + standardLimit <= modelCap) {
return standardLimit
}
return modelCap - budgetTokens
}
}
return standardLimit
export function maxOutputTokens(model: Provider.Model): number {
return Math.min(model.limit.output, OUTPUT_TOKEN_MAX) || OUTPUT_TOKEN_MAX
}
export function schema(model: Provider.Model, schema: JSONSchema.BaseSchema | JSONSchema7): JSONSchema7 {
+2 -3
View File
@@ -64,7 +64,7 @@ export const FileRoutes = lazy(() =>
z.object({
query: z.string(),
dirs: z.enum(["true", "false"]).optional(),
type: z.enum(["file", "directory"]).optional(),
type: z.enum(["file", "directory", "all"]).optional(),
limit: z.coerce.number().int().min(1).max(200).optional(),
}),
),
@@ -76,8 +76,7 @@ export const FileRoutes = lazy(() =>
const results = await File.search({
query,
limit: limit ?? 10,
dirs: dirs !== "false",
type,
type: dirs === "false" ? "file" : type,
})
return c.json(results)
},
+41 -8
View File
@@ -6,7 +6,6 @@ import { Instance } from "../project/instance"
import { Provider } from "../provider/provider"
import { MessageV2 } from "./message-v2"
import z from "zod"
import { SessionPrompt } from "./prompt"
import { Token } from "../util/token"
import { Log } from "../util/log"
import { SessionProcessor } from "./processor"
@@ -14,6 +13,7 @@ import { fn } from "@/util/fn"
import { Agent } from "@/agent/agent"
import { Plugin } from "@/plugin"
import { Config } from "@/config/config"
import { ProviderTransform } from "@/provider/transform"
export namespace SessionCompaction {
const log = Log.create({ service: "session.compaction" })
@@ -27,15 +27,22 @@ export namespace SessionCompaction {
),
}
const COMPACTION_BUFFER = 20_000
export async function isOverflow(input: { tokens: MessageV2.Assistant["tokens"]; model: Provider.Model }) {
const config = await Config.get()
if (config.compaction?.auto === false) return false
const context = input.model.limit.context
if (context === 0) return false
const count = input.tokens.input + input.tokens.cache.read + input.tokens.output
const output = Math.min(input.model.limit.output, SessionPrompt.OUTPUT_TOKEN_MAX) || SessionPrompt.OUTPUT_TOKEN_MAX
const usable = input.model.limit.input || context - output
return count > usable
const count =
input.tokens.total ||
input.tokens.input + input.tokens.output + input.tokens.cache.read + input.tokens.cache.write
const reserved =
config.compaction?.reserved ?? Math.min(COMPACTION_BUFFER, ProviderTransform.maxOutputTokens(input.model))
const usable = input.model.limit.input ? input.model.limit.input - reserved : context - reserved
return count >= usable
}
export const PRUNE_MINIMUM = 20_000
@@ -139,8 +146,34 @@ export namespace SessionCompaction {
{ sessionID: input.sessionID },
{ context: [], prompt: undefined },
)
const defaultPrompt =
"Provide a detailed prompt for continuing our conversation above. Focus on information that would be helpful for continuing the conversation, including what we did, what we're doing, which files we're working on, and what we're going to do next considering new session will not have access to our conversation."
const defaultPrompt = `Provide a detailed prompt for continuing our conversation above.
Focus on information that would be helpful for continuing the conversation, including what we did, what we're doing, which files we're working on, and what we're going to do next.
The summary that you construct will be used so that another agent can read it and continue the work.
When constructing the summary, try to stick to this template:
---
## Goal
[What goal(s) is the user trying to accomplish?]
## Instructions
- [What important instructions did the user give you that are relevant]
- [If there is a plan or spec, include information about it so next agent can continue using it]
## Discoveries
[What notable things were learned during this conversation that would be useful for the next agent to know when continuing the work]
## Accomplished
[What work has been completed, what work is still in progress, and what work is left?]
## Relevant files / directories
[Construct a structured list of relevant files that have been read, edited, or created that pertain to the task at hand. If all the files in a directory are relevant, include the path to the directory.]
---`
const promptText = compacting.prompt ?? [defaultPrompt, ...compacting.context].join("\n\n")
const result = await processor.process({
user: userMessage,
@@ -181,7 +214,7 @@ export namespace SessionCompaction {
sessionID: input.sessionID,
type: "text",
synthetic: true,
text: "Continue if you have next steps",
text: "Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed.",
time: {
start: Date.now(),
end: Date.now(),
+45 -19
View File
@@ -4,7 +4,7 @@ import { BusEvent } from "@/bus/bus-event"
import { Bus } from "@/bus"
import { Decimal } from "decimal.js"
import z from "zod"
import { type LanguageModelUsage, type ProviderMetadata } from "ai"
import { type ProviderMetadata } from "ai"
import { Config } from "../config/config"
import { Flag } from "../flag/flag"
import { Identifier } from "../id/id"
@@ -22,6 +22,8 @@ import { Snapshot } from "@/snapshot"
import type { Provider } from "@/provider/provider"
import { PermissionNext } from "@/permission/next"
import { Global } from "@/global"
import type { LanguageModelV2Usage } from "@ai-sdk/provider"
import { iife } from "@/util/iife"
export namespace Session {
const log = Log.create({ service: "session" })
@@ -439,34 +441,58 @@ export namespace Session {
export const getUsage = fn(
z.object({
model: z.custom<Provider.Model>(),
usage: z.custom<LanguageModelUsage>(),
usage: z.custom<LanguageModelV2Usage>(),
metadata: z.custom<ProviderMetadata>().optional(),
}),
(input) => {
const cacheReadInputTokens = input.usage.cachedInputTokens ?? 0
const cacheWriteInputTokens = (input.metadata?.["anthropic"]?.["cacheCreationInputTokens"] ??
// @ts-expect-error
input.metadata?.["bedrock"]?.["usage"]?.["cacheWriteInputTokens"] ??
// @ts-expect-error
input.metadata?.["venice"]?.["usage"]?.["cacheCreationInputTokens"] ??
0) as number
const excludesCachedTokens = !!(input.metadata?.["anthropic"] || input.metadata?.["bedrock"])
const adjustedInputTokens = excludesCachedTokens
? (input.usage.inputTokens ?? 0)
: (input.usage.inputTokens ?? 0) - cacheReadInputTokens - cacheWriteInputTokens
const safe = (value: number) => {
if (!Number.isFinite(value)) return 0
return value
}
const inputTokens = safe(input.usage.inputTokens ?? 0)
const outputTokens = safe(input.usage.outputTokens ?? 0)
const reasoningTokens = safe(input.usage.reasoningTokens ?? 0)
const cacheReadInputTokens = safe(input.usage.cachedInputTokens ?? 0)
const cacheWriteInputTokens = safe(
(input.metadata?.["anthropic"]?.["cacheCreationInputTokens"] ??
// @ts-expect-error
input.metadata?.["bedrock"]?.["usage"]?.["cacheWriteInputTokens"] ??
// @ts-expect-error
input.metadata?.["venice"]?.["usage"]?.["cacheCreationInputTokens"] ??
0) as number,
)
// OpenRouter provides inputTokens as the total count of input tokens (including cached).
// AFAIK other providers (OpenRouter/OpenAI/Gemini etc.) do it the same way e.g. vercel/ai#8794 (comment)
// Anthropic does it differently though - inputTokens doesn't include cached tokens.
// It looks like OpenCode's cost calculation assumes all providers return inputTokens the same way Anthropic does (I'm guessing getUsage logic was originally implemented with anthropic), so it's causing incorrect cost calculation for OpenRouter and others.
const excludesCachedTokens = !!(input.metadata?.["anthropic"] || input.metadata?.["bedrock"])
const adjustedInputTokens = safe(
excludesCachedTokens ? inputTokens : inputTokens - cacheReadInputTokens - cacheWriteInputTokens,
)
const total = iife(() => {
// Anthropic doesn't provide total_tokens, also ai sdk will vastly undercount if we
// don't compute from components
if (
input.model.api.npm === "@ai-sdk/anthropic" ||
input.model.api.npm === "@ai-sdk/amazon-bedrock" ||
input.model.api.npm === "@ai-sdk/google-vertex/anthropic"
) {
return adjustedInputTokens + outputTokens + cacheReadInputTokens + cacheWriteInputTokens
}
return input.usage.totalTokens
})
const tokens = {
input: safe(adjustedInputTokens),
output: safe(input.usage.outputTokens ?? 0),
reasoning: safe(input.usage?.reasoningTokens ?? 0),
total,
input: adjustedInputTokens,
output: outputTokens,
reasoning: reasoningTokens,
cache: {
write: safe(cacheWriteInputTokens),
read: safe(cacheReadInputTokens),
write: cacheWriteInputTokens,
read: cacheReadInputTokens,
},
}
+2 -10
View File
@@ -25,8 +25,7 @@ import { Auth } from "@/auth"
export namespace LLM {
const log = Log.create({ service: "llm" })
export const OUTPUT_TOKEN_MAX = Flag.OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX || 32_000
export const OUTPUT_TOKEN_MAX = ProviderTransform.OUTPUT_TOKEN_MAX
export type StreamInput = {
user: MessageV2.User
@@ -149,14 +148,7 @@ export namespace LLM {
)
const maxOutputTokens =
isCodex || provider.id.includes("github-copilot")
? undefined
: ProviderTransform.maxOutputTokens(
input.model.api.npm,
params.options,
input.model.limit.output,
OUTPUT_TOKEN_MAX,
)
isCodex || provider.id.includes("github-copilot") ? undefined : ProviderTransform.maxOutputTokens(input.model)
const tools = await resolveTools(input)
@@ -210,6 +210,7 @@ export namespace MessageV2 {
snapshot: z.string().optional(),
cost: z.number(),
tokens: z.object({
total: z.number().optional(),
input: z.number(),
output: z.number(),
reasoning: z.number(),
@@ -383,6 +384,7 @@ export namespace MessageV2 {
summary: z.boolean().optional(),
cost: z.number(),
tokens: z.object({
total: z.number().optional(),
input: z.number(),
output: z.number(),
reasoning: z.number(),
@@ -342,6 +342,9 @@ export namespace SessionProcessor {
stack: JSON.stringify(e.stack),
})
const error = MessageV2.fromError(e, { providerID: input.model.providerID })
if (MessageV2.ContextOverflowError.isInstance(error)) {
// TODO: Handle context overflow error
}
const retry = SessionRetry.retryable(error)
if (retry !== undefined) {
attempt++
-1
View File
@@ -52,7 +52,6 @@ globalThis.AI_SDK_LOG_WARNINGS = false
export namespace SessionPrompt {
const log = Log.create({ service: "session.prompt" })
export const OUTPUT_TOKEN_MAX = Flag.OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX || 32_000
const state = Instance.state(
() => {
+1 -2
View File
@@ -59,9 +59,8 @@ export namespace SessionRetry {
}
export function retryable(error: ReturnType<NamedError["toObject"]>) {
// DO NOT retry context overflow errors
// context overflow errors should not be retried
if (MessageV2.ContextOverflowError.isInstance(error)) return undefined
if (MessageV2.APIError.isInstance(error)) {
if (!error.data.isRetryable) return undefined
if (error.data.responseBody?.includes("FreeUsageLimitError"))
+52
View File
@@ -0,0 +1,52 @@
import { describe, expect, test } from "bun:test"
import path from "path"
import { tmpdir } from "../fixture/fixture"
import { FFF } from "../../src/file/fff"
import { File } from "../../src/file"
import { Instance } from "../../src/project/instance"
describe("file.fff", () => {
test("returns files and supports directory search via File.search", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(path.join(dir, "src", "app", "index.ts"), "export const app = true")
await Bun.write(path.join(dir, "src", "app", "util.ts"), "export const util = true")
await Bun.write(path.join(dir, "docs", "guide.md"), "# guide")
},
})
const files = await FFF.search({
cwd: tmp.path,
query: "index",
limit: 20,
})
expect(files.includes(path.join("src", "app", "index.ts"))).toBe(true)
await Instance.provide({
directory: tmp.path,
fn: async () => {
const found = await File.search({
query: "index",
type: "file",
limit: 20,
})
expect(found.includes(path.join("src", "app", "index.ts"))).toBe(true)
const dirs = await File.search({
query: "app",
type: "directory",
limit: 20,
})
expect(dirs.includes("src/app/")).toBe(true)
const all = await File.search({
query: "app",
type: "all",
limit: 20,
})
expect(all.includes(path.join("src", "app", "index.ts"))).toBe(true)
expect(all.includes("src/app/")).toBe(true)
},
})
})
})
@@ -175,100 +175,6 @@ describe("ProviderTransform.options - gpt-5 textVerbosity", () => {
})
})
describe("ProviderTransform.maxOutputTokens", () => {
test("returns 32k when modelLimit > 32k", () => {
const modelLimit = 100000
const result = ProviderTransform.maxOutputTokens("@ai-sdk/openai", {}, modelLimit, OUTPUT_TOKEN_MAX)
expect(result).toBe(OUTPUT_TOKEN_MAX)
})
test("returns modelLimit when modelLimit < 32k", () => {
const modelLimit = 16000
const result = ProviderTransform.maxOutputTokens("@ai-sdk/openai", {}, modelLimit, OUTPUT_TOKEN_MAX)
expect(result).toBe(16000)
})
describe("azure", () => {
test("returns 32k when modelLimit > 32k", () => {
const modelLimit = 100000
const result = ProviderTransform.maxOutputTokens("@ai-sdk/azure", {}, modelLimit, OUTPUT_TOKEN_MAX)
expect(result).toBe(OUTPUT_TOKEN_MAX)
})
test("returns modelLimit when modelLimit < 32k", () => {
const modelLimit = 16000
const result = ProviderTransform.maxOutputTokens("@ai-sdk/azure", {}, modelLimit, OUTPUT_TOKEN_MAX)
expect(result).toBe(16000)
})
})
describe("bedrock", () => {
test("returns 32k when modelLimit > 32k", () => {
const modelLimit = 100000
const result = ProviderTransform.maxOutputTokens("@ai-sdk/amazon-bedrock", {}, modelLimit, OUTPUT_TOKEN_MAX)
expect(result).toBe(OUTPUT_TOKEN_MAX)
})
test("returns modelLimit when modelLimit < 32k", () => {
const modelLimit = 16000
const result = ProviderTransform.maxOutputTokens("@ai-sdk/amazon-bedrock", {}, modelLimit, OUTPUT_TOKEN_MAX)
expect(result).toBe(16000)
})
})
describe("anthropic without thinking options", () => {
test("returns 32k when modelLimit > 32k", () => {
const modelLimit = 100000
const result = ProviderTransform.maxOutputTokens("@ai-sdk/anthropic", {}, modelLimit, OUTPUT_TOKEN_MAX)
expect(result).toBe(OUTPUT_TOKEN_MAX)
})
test("returns modelLimit when modelLimit < 32k", () => {
const modelLimit = 16000
const result = ProviderTransform.maxOutputTokens("@ai-sdk/anthropic", {}, modelLimit, OUTPUT_TOKEN_MAX)
expect(result).toBe(16000)
})
})
describe("anthropic with thinking options", () => {
test("returns 32k when budgetTokens + 32k <= modelLimit", () => {
const modelLimit = 100000
const options = {
thinking: {
type: "enabled",
budgetTokens: 10000,
},
}
const result = ProviderTransform.maxOutputTokens("@ai-sdk/anthropic", options, modelLimit, OUTPUT_TOKEN_MAX)
expect(result).toBe(OUTPUT_TOKEN_MAX)
})
test("returns modelLimit - budgetTokens when budgetTokens + 32k > modelLimit", () => {
const modelLimit = 50000
const options = {
thinking: {
type: "enabled",
budgetTokens: 30000,
},
}
const result = ProviderTransform.maxOutputTokens("@ai-sdk/anthropic", options, modelLimit, OUTPUT_TOKEN_MAX)
expect(result).toBe(20000)
})
test("returns 32k when thinking type is not enabled", () => {
const modelLimit = 100000
const options = {
thinking: {
type: "disabled",
budgetTokens: 10000,
},
}
const result = ProviderTransform.maxOutputTokens("@ai-sdk/anthropic", options, modelLimit, OUTPUT_TOKEN_MAX)
expect(result).toBe(OUTPUT_TOKEN_MAX)
})
})
})
describe("ProviderTransform.schema - gemini array items", () => {
test("adds missing items for array properties", () => {
const geminiModel = {
@@ -15,6 +15,7 @@ function createModel(opts: {
output: number
input?: number
cost?: Provider.Model["cost"]
npm?: string
}): Provider.Model {
return {
id: "test-model",
@@ -34,7 +35,7 @@ function createModel(opts: {
input: { text: true, image: false, audio: false, video: false },
output: { text: true, image: false, audio: false, video: false },
},
api: { npm: "@ai-sdk/anthropic" },
api: { npm: opts.npm ?? "@ai-sdk/anthropic" },
options: {},
} as Provider.Model
}
@@ -70,7 +71,7 @@ describe("session.compaction.isOverflow", () => {
directory: tmp.path,
fn: async () => {
const model = createModel({ context: 100_000, output: 32_000 })
const tokens = { input: 50_000, output: 10_000, reasoning: 0, cache: { read: 10_000, write: 0 } }
const tokens = { input: 60_000, output: 10_000, reasoning: 0, cache: { read: 10_000, write: 0 } }
expect(await SessionCompaction.isOverflow({ tokens, model })).toBe(true)
},
})
@@ -112,6 +113,86 @@ describe("session.compaction.isOverflow", () => {
})
})
// ─── Bug reproduction tests ───────────────────────────────────────────
// These tests demonstrate that when limit.input is set, isOverflow()
// does not subtract any headroom for the next model response. This means
// compaction only triggers AFTER we've already consumed the full input
// budget, leaving zero room for the next API call's output tokens.
//
// Compare: without limit.input, usable = context - output (reserves space).
// With limit.input, usable = limit.input (reserves nothing).
//
// Related issues: #10634, #8089, #11086, #12621
// Open PRs: #6875, #12924
test("BUG: no headroom when limit.input is set — compaction should trigger near boundary but does not", async () => {
await using tmp = await tmpdir()
await Instance.provide({
directory: tmp.path,
fn: async () => {
// Simulate Claude with prompt caching: input limit = 200K, output limit = 32K
const model = createModel({ context: 200_000, input: 200_000, output: 32_000 })
// We've used 198K tokens total. Only 2K under the input limit.
// On the next turn, the full conversation (198K) becomes input,
// plus the model needs room to generate output — this WILL overflow.
const tokens = { input: 180_000, output: 15_000, reasoning: 0, cache: { read: 3_000, write: 0 } }
// count = 180K + 3K + 15K = 198K
// usable = limit.input = 200K (no output subtracted!)
// 198K > 200K = false → no compaction triggered
// WITHOUT limit.input: usable = 200K - 32K = 168K, and 198K > 168K = true ✓
// WITH limit.input: usable = 200K, and 198K > 200K = false ✗
// With 198K used and only 2K headroom, the next turn will overflow.
// Compaction MUST trigger here.
expect(await SessionCompaction.isOverflow({ tokens, model })).toBe(true)
},
})
})
test("BUG: without limit.input, same token count correctly triggers compaction", async () => {
await using tmp = await tmpdir()
await Instance.provide({
directory: tmp.path,
fn: async () => {
// Same model but without limit.input — uses context - output instead
const model = createModel({ context: 200_000, output: 32_000 })
// Same token usage as above
const tokens = { input: 180_000, output: 15_000, reasoning: 0, cache: { read: 3_000, write: 0 } }
// count = 198K
// usable = context - output = 200K - 32K = 168K
// 198K > 168K = true → compaction correctly triggered
const result = await SessionCompaction.isOverflow({ tokens, model })
expect(result).toBe(true) // ← Correct: headroom is reserved
},
})
})
test("BUG: asymmetry — limit.input model allows 30K more usage before compaction than equivalent model without it", async () => {
await using tmp = await tmpdir()
await Instance.provide({
directory: tmp.path,
fn: async () => {
// Two models with identical context/output limits, differing only in limit.input
const withInputLimit = createModel({ context: 200_000, input: 200_000, output: 32_000 })
const withoutInputLimit = createModel({ context: 200_000, output: 32_000 })
// 170K total tokens — well above context-output (168K) but below input limit (200K)
const tokens = { input: 166_000, output: 10_000, reasoning: 0, cache: { read: 5_000, write: 0 } }
const withLimit = await SessionCompaction.isOverflow({ tokens, model: withInputLimit })
const withoutLimit = await SessionCompaction.isOverflow({ tokens, model: withoutInputLimit })
// Both models have identical real capacity — they should agree:
expect(withLimit).toBe(true) // should compact (170K leaves no room for 32K output)
expect(withoutLimit).toBe(true) // correctly compacts (170K > 168K)
},
})
})
test("returns false when model context limit is 0", async () => {
await using tmp = await tmpdir()
await Instance.provide({
@@ -290,4 +371,53 @@ describe("session.getUsage", () => {
expect(result.cost).toBe(3 + 1.5)
})
test.each(["@ai-sdk/anthropic", "@ai-sdk/amazon-bedrock", "@ai-sdk/google-vertex/anthropic"])(
"computes total from components for %s models",
(npm) => {
const model = createModel({ context: 100_000, output: 32_000, npm })
const usage = {
inputTokens: 1000,
outputTokens: 500,
// These providers typically report total as input + output only,
// excluding cache read/write.
totalTokens: 1500,
cachedInputTokens: 200,
}
if (npm === "@ai-sdk/amazon-bedrock") {
const result = Session.getUsage({
model,
usage,
metadata: {
bedrock: {
usage: {
cacheWriteInputTokens: 300,
},
},
},
})
expect(result.tokens.input).toBe(1000)
expect(result.tokens.cache.read).toBe(200)
expect(result.tokens.cache.write).toBe(300)
expect(result.tokens.total).toBe(2000)
return
}
const result = Session.getUsage({
model,
usage,
metadata: {
anthropic: {
cacheCreationInputTokens: 300,
},
},
})
expect(result.tokens.input).toBe(1000)
expect(result.tokens.cache.read).toBe(200)
expect(result.tokens.cache.write).toBe(300)
expect(result.tokens.total).toBe(2000)
},
)
})
+4 -28
View File
@@ -314,12 +314,7 @@ describe("session.llm.stream", () => {
expect(body.stream).toBe(true)
const maxTokens = (body.max_tokens as number | undefined) ?? (body.max_output_tokens as number | undefined)
const expectedMaxTokens = ProviderTransform.maxOutputTokens(
resolved.api.npm,
ProviderTransform.options({ model: resolved, sessionID }),
resolved.limit.output,
LLM.OUTPUT_TOKEN_MAX,
)
const expectedMaxTokens = ProviderTransform.maxOutputTokens(resolved)
expect(maxTokens).toBe(expectedMaxTokens)
const reasoning = (body.reasoningEffort as string | undefined) ?? (body.reasoning_effort as string | undefined)
@@ -442,12 +437,7 @@ describe("session.llm.stream", () => {
expect((body.reasoning as { effort?: string } | undefined)?.effort).toBe("high")
const maxTokens = body.max_output_tokens as number | undefined
const expectedMaxTokens = ProviderTransform.maxOutputTokens(
resolved.api.npm,
ProviderTransform.options({ model: resolved, sessionID }),
resolved.limit.output,
LLM.OUTPUT_TOKEN_MAX,
)
const expectedMaxTokens = ProviderTransform.maxOutputTokens(resolved)
expect(maxTokens).toBe(expectedMaxTokens)
},
})
@@ -565,14 +555,7 @@ describe("session.llm.stream", () => {
expect(capture.url.pathname.endsWith("/messages")).toBe(true)
expect(body.model).toBe(resolved.api.id)
expect(body.max_tokens).toBe(
ProviderTransform.maxOutputTokens(
resolved.api.npm,
ProviderTransform.options({ model: resolved, sessionID }),
resolved.limit.output,
LLM.OUTPUT_TOKEN_MAX,
),
)
expect(body.max_tokens).toBe(ProviderTransform.maxOutputTokens(resolved))
expect(body.temperature).toBe(0.4)
expect(body.top_p).toBe(0.9)
},
@@ -677,14 +660,7 @@ describe("session.llm.stream", () => {
expect(capture.url.pathname).toBe(pathSuffix)
expect(config?.temperature).toBe(0.3)
expect(config?.topP).toBe(0.8)
expect(config?.maxOutputTokens).toBe(
ProviderTransform.maxOutputTokens(
resolved.api.npm,
ProviderTransform.options({ model: resolved, sessionID }),
resolved.limit.output,
LLM.OUTPUT_TOKEN_MAX,
),
)
expect(config?.maxOutputTokens).toBe(ProviderTransform.maxOutputTokens(resolved))
},
})
})
@@ -112,6 +112,15 @@ describe("session.retry.retryable", () => {
const error = wrap("not-json")
expect(SessionRetry.retryable(error)).toBeUndefined()
})
test("does not retry context overflow errors", () => {
const error = new MessageV2.ContextOverflowError({
message: "Input exceeds context window of this model",
responseBody: '{"error":{"code":"context_length_exceeded"}}',
}).toObject() as ReturnType<NamedError["toObject"]>
expect(SessionRetry.retryable(error)).toBeUndefined()
})
})
describe("session.message-v2.fromError", () => {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/plugin",
"version": "1.1.55",
"version": "1.1.56",
"type": "module",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/sdk",
"version": "1.1.55",
"version": "1.1.56",
"type": "module",
"license": "MIT",
"scripts": {
+6 -4
View File
@@ -203,6 +203,7 @@ export type AssistantMessage = {
summary?: boolean
cost: number
tokens: {
total?: number
input: number
output: number
reasoning: number
@@ -418,6 +419,7 @@ export type StepFinishPart = {
snapshot?: string
cost: number
tokens: {
total?: number
input: number
output: number
reasoning: number
@@ -1030,10 +1032,6 @@ export type KeybindsConfig = {
* Toggle model favorite status
*/
model_favorite_toggle?: string
/**
* Toggle showing all models
*/
model_show_all_toggle?: string
/**
* Share current session
*/
@@ -1826,6 +1824,10 @@ export type Config = {
* Enable pruning of old tool outputs (default: true)
*/
prune?: boolean
/**
* Token buffer for compaction. Leaves enough window to avoid overflow during compaction.
*/
reserved?: number
}
experimental?: {
disable_paste_summary?: boolean
+12
View File
@@ -6393,6 +6393,9 @@
"tokens": {
"type": "object",
"properties": {
"total": {
"type": "number"
},
"input": {
"type": "number"
},
@@ -7023,6 +7026,9 @@
"tokens": {
"type": "object",
"properties": {
"total": {
"type": "number"
},
"input": {
"type": "number"
},
@@ -9894,6 +9900,12 @@
"prune": {
"description": "Enable pruning of old tool outputs (default: true)",
"type": "boolean"
},
"reserved": {
"description": "Token buffer for compaction. Leaves enough window to avoid overflow during compaction.",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
}
},
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/slack",
"version": "1.1.55",
"version": "1.1.56",
"type": "module",
"license": "MIT",
"scripts": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/ui",
"version": "1.1.55",
"version": "1.1.56",
"type": "module",
"license": "MIT",
"exports": {
+70 -32
View File
@@ -877,6 +877,74 @@ ToolRegistry.register({
const data = useData()
const i18n = useI18n()
const childSessionId = () => props.metadata.sessionId as string | undefined
const href = createMemo(() => {
const sessionId = childSessionId()
if (!sessionId) return
const direct = data.sessionHref?.(sessionId)
if (direct) return direct
if (typeof window === "undefined") return
const path = window.location.pathname
const idx = path.indexOf("/session")
if (idx === -1) return
return `${path.slice(0, idx)}/session/${sessionId}`
})
createEffect(() => {
const sessionId = childSessionId()
if (!sessionId) return
const sync = data.syncSession
if (!sync) return
Promise.resolve(sync(sessionId)).catch(() => undefined)
})
const handleLinkClick = (e: MouseEvent) => {
const sessionId = childSessionId()
const url = href()
if (!sessionId || !url) return
e.stopPropagation()
if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return
const nav = data.navigateToSession
if (!nav || typeof window === "undefined") return
e.preventDefault()
const before = window.location.pathname + window.location.search + window.location.hash
nav(sessionId)
setTimeout(() => {
const after = window.location.pathname + window.location.search + window.location.hash
if (after === before) window.location.assign(url)
}, 50)
}
const trigger = () => (
<div data-slot="basic-tool-tool-info-structured">
<div data-slot="basic-tool-tool-info-main">
<span data-slot="basic-tool-tool-title" class="capitalize">
{i18n.t("ui.tool.agent", { type: props.input.subagent_type || props.tool })}
</span>
<Show when={props.input.description}>
<Switch>
<Match when={href()}>
{(url) => (
<a data-slot="basic-tool-tool-subtitle" class="clickable" href={url()} onClick={handleLinkClick}>
{props.input.description}
</a>
)}
</Match>
<Match when={true}>
<span data-slot="basic-tool-tool-subtitle">{props.input.description}</span>
</Match>
</Switch>
</Show>
</div>
</div>
)
const childToolParts = createMemo(() => {
const sessionId = childSessionId()
if (!sessionId) return []
@@ -924,13 +992,6 @@ ToolRegistry.register({
})
}
const handleSubtitleClick = () => {
const sessionId = childSessionId()
if (sessionId && data.navigateToSession) {
data.navigateToSession(sessionId)
}
}
const renderChildToolPart = () => {
const toolData = childToolPart()
if (!toolData) return null
@@ -958,21 +1019,7 @@ ToolRegistry.register({
<Switch>
<Match when={childPermission()}>
<>
<Show
when={childToolPart()}
fallback={
<BasicTool
icon="task"
defaultOpen={true}
trigger={{
title: i18n.t("ui.tool.agent", { type: props.input.subagent_type || props.tool }),
titleClass: "capitalize",
subtitle: props.input.description,
}}
onSubtitleClick={handleSubtitleClick}
/>
}
>
<Show when={childToolPart()} fallback={<BasicTool icon="task" defaultOpen={true} trigger={trigger()} />}>
{renderChildToolPart()}
</Show>
<div data-component="permission-prompt">
@@ -991,16 +1038,7 @@ ToolRegistry.register({
</>
</Match>
<Match when={true}>
<BasicTool
icon="task"
defaultOpen={true}
trigger={{
title: i18n.t("ui.tool.agent", { type: props.input.subagent_type || props.tool }),
titleClass: "capitalize",
subtitle: props.input.description,
}}
onSubtitleClick={handleSubtitleClick}
>
<BasicTool icon="task" defaultOpen={true} trigger={trigger()}>
<div
ref={autoScroll.scrollRef}
onScroll={autoScroll.handleScroll}
+8
View File
@@ -48,6 +48,10 @@ export type QuestionRejectFn = (input: { requestID: string }) => void
export type NavigateToSessionFn = (sessionID: string) => void
export type SessionHrefFn = (sessionID: string) => string
export type SyncSessionFn = (sessionID: string) => void | Promise<void>
export const { use: useData, provider: DataProvider } = createSimpleContext({
name: "Data",
init: (props: {
@@ -57,6 +61,8 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
onQuestionReply?: QuestionReplyFn
onQuestionReject?: QuestionRejectFn
onNavigateToSession?: NavigateToSessionFn
onSessionHref?: SessionHrefFn
onSyncSession?: SyncSessionFn
}) => {
return {
get store() {
@@ -69,6 +75,8 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
replyToQuestion: props.onQuestionReply,
rejectQuestion: props.onQuestionReject,
navigateToSession: props.onNavigateToSession,
sessionHref: props.onSessionHref,
syncSession: props.onSyncSession,
}
},
})
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/util",
"version": "1.1.55",
"version": "1.1.56",
"private": true,
"type": "module",
"license": "MIT",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "@opencode-ai/web",
"type": "module",
"license": "MIT",
"version": "1.1.55",
"version": "1.1.56",
"scripts": {
"dev": "astro dev",
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
+1 -1
View File
@@ -149,7 +149,7 @@ Neke ugrađene komande kao što su `/undo` i `/redo` trenutno nisu podržane.
:::
- Ugrađeni alati (operacije sa datotekama, naredbe terminala, itd.)
- Prilagođeni alati i komande kosih crta
- Prilagođeni alati i slash komande
- MCP serveri konfigurisani u vašoj OpenCode konfiguraciji
- Pravila specifična za projekat `AGENTS.md`
- Prilagođeni formateri i linteri
+1 -1
View File
@@ -593,5 +593,5 @@ Ove varijable okruženja omogućavaju eksperimentalne karakteristike koje se mog
| `OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER` | boolean | Onemogući praćenje datoteka |
| `OPENCODE_EXPERIMENTAL_EXA` | boolean | Omogući eksperimentalne Exa funkcije |
| `OPENCODE_EXPERIMENTAL_LSP_TY` | boolean | Omogući eksperimentalnu provjeru tipa LSP |
| `OPENCODE_EXPERIMENTAL_MARKDOWN` | boolean | Omogući eksperimentalne funkcije smanjenja |
| `OPENCODE_EXPERIMENTAL_MARKDOWN` | boolean | Omogući eksperimentalne Markdown funkcije |
| `OPENCODE_EXPERIMENTAL_PLAN_MODE` | boolean | Omogući režim plana |
+7 -7
View File
@@ -93,7 +93,7 @@ Globalna konfiguracija poništava zadane postavke udaljene organizacije.
---
### Nakon projekta
### Project
Dodajte `opencode.json` u korijen projekta. Konfiguracija projekta ima najveći prioritet među standardnim konfiguracijskim datotekama - ona nadjačava globalne i udaljene konfiguracije.
:::tip
@@ -104,7 +104,7 @@ Ovo je također sigurno provjeriti u Git i koristi istu shemu kao globalna.
---
### Custom track
### Custom config
Navedite prilagođenu putanju konfiguracijske datoteke koristeći varijablu okruženja `OPENCODE_CONFIG`.
@@ -117,7 +117,7 @@ Prilagođena konfiguracija se učitava između globalne i projektne konfiguracij
---
### Prilagođeni imenik
### Custom directory
Navedite prilagođeni konfiguracijski direktorij koristeći `OPENCODE_CONFIG_DIR`
varijabla okruženja. U ovom direktoriju će se tražiti agenti, komande,
@@ -194,7 +194,7 @@ Dostupne opcije:
---
### Uvijek
### Tools
Možete upravljati alatima koje LLM može koristiti putem opcije `tools`.
@@ -281,7 +281,7 @@ Amazon Bedrock podržava konfiguraciju specifičnu za AWS:
---
### Domaći
### Theme
Možete konfigurirati temu koju želite koristiti u svojoj OpenCode konfiguraciji putem opcije `theme`.
@@ -358,7 +358,7 @@ Ovo traje:
---
### komandante
### Command
Možete konfigurirati prilagođene komande za ponavljanje zadataka putem opcije `command`.
@@ -415,7 +415,7 @@ Imajte na umu da ovo funkcionira samo ako nije instalirano pomoću upravitelja p
---
### Trenerke
### Formatters
Možete konfigurirati formatere koda putem opcije `formatter`.
@@ -1,5 +1,5 @@
---
title: Trenerke
title: Formatters
description: OpenCode koristi formatere specifične za jezik.
---
@@ -14,8 +14,8 @@ OpenCode dolazi sa nekoliko ugrađenih formatera za popularne jezike i okvire. I
|-------------------- | -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| gofmt | .go | `gofmt` komanda dostupna |
| mix | .ex, .exs, .eex, .heex, .leex, .neex, .sface | `mix` komanda dostupna |
| ljepše | .js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml i [više](https://prettier.io/docs/en/index.html) | `prettier` zavisnost u `package.json` |
| biom | .js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml i [više](https://biomejs.dev/) | `biome.json(c)` konfiguracijski fajl |
| prettier | .js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml i [više](https://prettier.io/docs/en/index.html) | `prettier` zavisnost u `package.json` |
| biome | .js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml i [više](https://biomejs.dev/) | `biome.json(c)` konfiguracijski fajl |
| zig | .zig, .zon | `zig` komanda dostupna |
| clang-format | .c, .cpp, .h, .hpp, .ino i [više](https://clang.llvm.org/docs/ClangFormat.html) | `.clang-format` konfiguracijski fajl |
| ktlint | .kt, .kts | `ktlint` komanda dostupna |
@@ -25,7 +25,7 @@ OpenCode dolazi sa nekoliko ugrađenih formatera za popularne jezike i okvire. I
| uv | .py, .pyi | `uv` komanda dostupna || rubocop | .rb, .rake, .gemspec, .ru | `rubocop` komanda dostupna |
| standardrb | .rb, .rake, .gemspec, .ru | `standardrb` komanda dostupna |
| htmlbeautifier | .erb, .html.erb | `htmlbeautifier` komanda dostupna |
| zrak | .R | `air` komanda dostupna |
| air | .R | `air` komanda dostupna |
| dart | .dart | `dart` komanda dostupna |
| ocamlformat | .ml, .mli | `ocamlformat` komanda dostupna i `.ocamlformat` konfiguracioni fajl |
| terraform | .tf, .tfvars | `terraform` komanda dostupna |
@@ -61,7 +61,7 @@ Možete prilagoditi formatere kroz `formatter` odjeljak u vašoj OpenCode konfig
```
Svaka konfiguracija formatera podržava sljedeće:
| Nekretnine | Vrsta | Opis
| Svojstvo | Vrsta | Opis
|------------- | -------- | ------------------------------------------------------- |
| `disabled` | boolean | Postavite ovo na `true` da onemogućite formater |
| `command` | string[] | Naredba za pokretanje za formatiranje |
+3 -3
View File
@@ -3,7 +3,7 @@ title: IDE
description: Ekstenzija OpenCode za VS Code, Cursor i druge IDE
---
OpenCode se integriše sa VS kodom, kursorom ili bilo kojim IDE-om koji podržava terminal. Samo pokrenite `opencode` u terminalu da započnete.
OpenCode se integriše sa VS kodom, Cursor-om ili bilo kojim IDE-om koji podržava terminal. Samo pokrenite `opencode` u terminalu da započnete.
---
@@ -40,8 +40,8 @@ Ako se ekstenzija ne uspije automatski instalirati:
- Uvjerite se da koristite `opencode` u integriranom terminalu.
- Potvrdite da je CLI za vaš IDE instaliran:
- Za VS kod: `code` naredbu
- Za kursor: `cursor` naredba
- Za jedrenje na dasci: `windsurf` komanda
- Za Cursor: `cursor` naredba
- Za Windsurf: `windsurf` komanda
- Za VSCodium: `codium` komanda
- Ako ne, pokrenite `Cmd+Shift+P` (Mac) ili `Ctrl+Shift+P` (Windows/Linux) i potražite "Shell Command: Install 'code' command in PATH" (ili ekvivalent za vaš IDE)
- Osigurajte da VS Code ima dozvolu za instaliranje ekstenzija
+2 -2
View File
@@ -8,7 +8,7 @@ import config from "../../../../config.mjs"
export const console = config.console
[**OpenCode**](/) je agent za AI kodiranje otvorenog koda. Dostupan je kao interfejs baziran na terminalu, desktop aplikacija ili IDE ekstenzija.
![OpenCode TUI sa temom otvorenog koda](../../../assets/lander/screenshot.png)
![OpenCode TUI sa opencode temom](../../../assets/lander/screenshot.png)
Hajde da počnemo.
---
@@ -90,7 +90,7 @@ Također ga možete instalirati pomoću sljedećih naredbi:
Za najbolje iskustvo na Windows-u preporučujemo korištenje [Windows Subsystem for Linux (WSL)](/docs/windows-wsl). Pruža bolje performanse i potpunu kompatibilnost sa OpenCode karakteristikama.
:::
- **Upotreba čokolade**
- **Upotreba Chocolatey-a**
```bash
choco install opencode
@@ -140,10 +140,10 @@ Unos prompta aplikacije OpenCode za desktop podržava uobičajene prečice u sti
| `alt+b` | Pomeri kursor za jednu reč unazad |
| `alt+f` | Pomeri kursor za jednu reč unapred |
| `ctrl+d` | Izbriši znak ispod kursora |
| `ctrl+k` | Ubiti do kraja reda |
| `ctrl+k` | Kill do kraja reda |
| `ctrl+u` | Kill do početka reda |
| `ctrl+w` | Ubiti prethodnu riječ |
| `alt+d` | Ubiti sljedeću riječ |
| `ctrl+w` | Kill prethodnu riječ |
| `alt+d` | Kill sljedeću riječ |
| `ctrl+t` | Transponirajte znakove |
| `ctrl+g` | Otkaži iskakanje / poništi odgovor na pokretanje |
+1 -1
View File
@@ -70,7 +70,7 @@ Možete prilagoditi LSP servere kroz `lsp` odjeljak u vašoj opencode konfigurac
```
Svaki LSP server podržava sljedeće:
| Nekretnine | Vrsta | Opis
| Svojstvo | Vrsta | Opis
|---------------- | -------- | ------------------------------------------------- |
| `disabled` | boolean | Postavite ovo na `true` da onemogućite LSP server |
| `command` | string[] | Naredba za pokretanje LSP servera |
@@ -105,7 +105,7 @@ use the mcp_everything tool to add the number 3 and 4
#### Opcije
Ovdje su sve opcije za konfiguriranje lokalnog MCP servera.
| Opcija | Vrsta | Obavezno | Opis
| Opcija | Tip | Obavezno | Opis
|------------- | ------- | -------- | ----------------------------------------------------------------------------------- |
| `type` | String | Y | Tip veze sa MCP serverom, mora biti `"local"`. |
| `command` | Niz | Y | Naredba i argumenti za pokretanje MCP servera. |
@@ -139,7 +139,7 @@ Dodajte udaljene MCP servere postavljanjem `type` na `"remote"`.
#### Opcije
| Opcija | Vrsta | Obavezno | Opis |
| Opcija | Tip | Obavezno | Opis |
| --------- | ------- | -------- | -------------------------------------------------------------------------------------------------- |
| `type` | String | Y | Tip veze sa MCP serverom, mora biti `"remote"`. |
| `url` | String | Y | URL udaljenog MCP servera. |
@@ -248,7 +248,7 @@ Ako želite onemogućiti automatski OAuth za server (npr. za servere koji umjest
#### OAuth opcije
| Opcija | Vrsta | Opis |
| Opcija | Tip | Opis |
| -------------- | --------------- | --------------------------------------------------------------------------------------------- |
| `oauth` | Objekt \| false | OAuth konfiguracijski objekt, ili `false` da onemogućite automatsko otkrivanje OAuth. |
| `clientId` | String | ID OAuth klijenta. Ako nije navedeno, pokušat će se izvršiti dinamička registracija klijenta. |
@@ -269,7 +269,7 @@ opencode mcp debug my-oauth-server
Komanda `mcp debug` prikazuje trenutni auth status, testira HTTP povezanost i pokušava OAuth discovery flow.
## Upravljaj
## Manage
Vaši MCP serveri su dostupni kao alati u OpenCode, zajedno s ugrađenim alatima. Možete njima upravljati kroz OpenCode konfiguraciju kao i bilo kojim drugim alatom.
+1 -1
View File
@@ -180,7 +180,7 @@ Možete nadjačati postojeće varijante ili dodati svoje:
}
```
### Varijante ciklusa
### Cycle variants
Koristite keybind `variant_cycle` za brzo prebacivanje između varijanti. [Saznajte više](/docs/keybinds).
+5 -5
View File
@@ -9,7 +9,7 @@ Načini se sada konfiguriraju preko opcije `agent` u konfiguraciji otvorenog kod
:::
Režimi u otvorenom kodu omogućavaju vam da prilagodite ponašanje, alate i upite za različite slučajeve upotrebe.
Dolazi sa dva ugrađena načina rada: **gradite** i **planirajte**. Možete prilagoditi
Dolazi sa dva ugrađena načina rada: **build** i **plan**. Možete prilagoditi
ove ili konfigurirajte svoje putem opencode config.
Možete se prebacivati između režima tokom sesije ili ih konfigurisati u svom konfiguracionom fajlu.
@@ -308,9 +308,9 @@ Priorities:
Evo nekoliko uobičajenih slučajeva upotrebe za različite načine rada.
- **Način izgradnje**: Potpuni razvojni rad sa svim omogućenim alatima
- **Način planiranja**: Analiza i planiranje bez izmjena
- **Build mode**: Potpuni razvojni rad sa svim omogućenim alatima
- **Plan mode**: Analiza i planiranje bez izmjena
- **Review mode**: Pregled koda sa pristupom samo za čitanje plus alati za dokumentaciju
- **Režim za otklanjanje grešaka**: Fokusiran na istragu sa omogućenim bash i alatima za čitanje
- **Režim dokumenata**: Pisanje dokumentacije sa operacijama datoteka, ali bez sistemskih naredbi
- **Debug mode**: Fokusiran na istragu sa omogućenim bash i alatima za čitanje
- **Docs mode**: Pisanje dokumentacije sa operacijama datoteka, ali bez sistemskih naredbi
Možda ćete također otkriti da su različiti modeli dobri za različite slučajeve upotrebe.
@@ -77,7 +77,7 @@ Uzorci dozvola koriste jednostavno podudaranje zamjenskih znakova:
- `?` odgovara tačno jednom znaku
- Svi ostali likovi se bukvalno podudaraju
### Proširenje kućnog imenika
### Proširenje kućnog direktorija
Možete koristiti `~` ili `$HOME` na početku obrasca da referencirate svoj početni direktorij. Ovo je posebno korisno za [`external_directory`](#external-directories) pravila.
@@ -85,7 +85,7 @@ Možete koristiti `~` ili `$HOME` na početku obrasca da referencirate svoj poč
- `$HOME/projects/*` -> `/Users/username/projects/*`
- `~` -> `/Users/username`
### Vanjski imenici
### Vanjski direktoriji
Koristite `external_directory` da dozvolite pozive alata koji dodiruju putanje izvan radnog direktorija gdje je OpenCode pokrenut. Ovo se odnosi na bilo koji alat koji uzima putanju kao ulaz (na primjer `read`, `edit`, `list`, `glob`, `grep` i mnoge `bash` komande).
Proširenje kuće (poput `~/...`) utiče samo na način na koji je obrazac napisan. Ne čini vanjsku stazu dijelom trenutnog radnog prostora, tako da staze izvan radnog direktorija i dalje moraju biti dozvoljene preko `external_directory`.
@@ -84,7 +84,7 @@ Radi kao i svaki drugi provajder u OpenCode i potpuno je opcionalan za korišten
---
## Imenik
## Directory
Pogledajmo neke od provajdera detaljno. Ako želite dodati provajdera na
listu, slobodno otvori PR.
+1 -1
View File
@@ -3,7 +3,7 @@ title: Pravila
description: Postavite prilagodena uputstva za opencode.
---
Mozete dodati prilagodena uputstva za opencode tako sto kreirate `AGENTS.md` datoteku. Ovo je slicno pravilima u Cursoru. Sadrzi uputstva koja se ubacuju u LLM kontekst da prilagode ponasanje za vas projekat.
Mozete dodati prilagodena uputstva za opencode tako sto kreirate `AGENTS.md` datoteku. Ovo je slicno pravilima u Cursor-u. Sadrzi uputstva koja se ubacuju u LLM kontekst da prilagode ponasanje za vas projekat.
---
+5 -5
View File
@@ -226,7 +226,7 @@ const { providers, default: defaults } = await client.config.providers()
### Sessions
| Method | Description | Notes |
| Metoda | Opis | Napomene |
| ---------------------------------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `session.list()` | List sessions | Returns <a href={typesUrl}><code>Session[]</code></a> |
| `session.get({ path })` | Get session | Returns <a href={typesUrl}><code>Session</code></a> |
@@ -283,7 +283,7 @@ await client.session.prompt({
### Files
| Method | Description | Response |
| Metoda | Opis | Odgovor |
| ------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------- |
| `find.text({ query })` | Search for text in files | Array of match objects with `path`, `lines`, `line_number`, `absolute_offset`, `submatches` |
| `find.files({ query })` | Find files and directories by name | `string[]` (paths) |
@@ -324,7 +324,7 @@ const content = await client.file.read({
### TUI
| Method | Description | Response |
| Metoda | Opis | Odgovor |
| ------------------------------ | ------------------------- | --------- |
| `tui.appendPrompt({ body })` | Append text to the prompt | `boolean` |
| `tui.openHelp()` | Open the help dialog | `boolean` |
@@ -355,7 +355,7 @@ await client.tui.showToast({
### Auth
| Method | Description | Response |
| Metoda | Opis | Odgovor |
| ------------------- | ------------------------------ | --------- |
| `auth.set({ ... })` | Set authentication credentials | `boolean` |
@@ -374,7 +374,7 @@ await client.auth.set({
### Events
| Method | Description | Response |
| Metoda | Opis | Odgovor |
| ------------------- | ------------------------- | ------------------------- |
| `event.subscribe()` | Server-sent events stream | Server-sent events stream |
+22 -22
View File
@@ -86,7 +86,7 @@ opencode server izlaže sljedece API-je.
### Global
| Method | Path | Description | Response |
| Metoda | Putanja | Opis | Odgovor |
| ------ | ---------------- | ------------------------------ | ------------------------------------ |
| `GET` | `/global/health` | Get server health and version | `{ healthy: true, version: string }` |
| `GET` | `/global/event` | Get global events (SSE stream) | Event stream |
@@ -95,7 +95,7 @@ opencode server izlaže sljedece API-je.
### Project
| Method | Path | Description | Response |
| Metoda | Putanja | Opis | Odgovor |
| ------ | ------------------ | ----------------------- | --------------------------------------------- |
| `GET` | `/project` | List all projects | <a href={typesUrl}><code>Project[]</code></a> |
| `GET` | `/project/current` | Get the current project | <a href={typesUrl}><code>Project</code></a> |
@@ -104,7 +104,7 @@ opencode server izlaže sljedece API-je.
### Path & VCS
| Method | Path | Description | Response |
| Metoda | Putanja | Opis | Odgovor |
| ------ | ------- | ------------------------------------ | ------------------------------------------- |
| `GET` | `/path` | Get the current path | <a href={typesUrl}><code>Path</code></a> |
| `GET` | `/vcs` | Get VCS info for the current project | <a href={typesUrl}><code>VcsInfo</code></a> |
@@ -113,7 +113,7 @@ opencode server izlaže sljedece API-je.
### Instance
| Method | Path | Description | Response |
| Metoda | Putanja | Opis | Odgovor |
| ------ | ------------------- | ---------------------------- | --------- |
| `POST` | `/instance/dispose` | Dispose the current instance | `boolean` |
@@ -121,7 +121,7 @@ opencode server izlaže sljedece API-je.
### Config
| Method | Path | Description | Response |
| Metoda | Putanja | Opis | Odgovor |
| ------- | ------------------- | --------------------------------- | ---------------------------------------------------------------------------------------- |
| `GET` | `/config` | Get config info | <a href={typesUrl}><code>Config</code></a> |
| `PATCH` | `/config` | Update config | <a href={typesUrl}><code>Config</code></a> |
@@ -131,7 +131,7 @@ opencode server izlaže sljedece API-je.
### Provider
| Method | Path | Description | Response |
| Metoda | Putanja | Opis | Odgovor |
| ------ | -------------------------------- | ------------------------------------ | ----------------------------------------------------------------------------------- |
| `GET` | `/provider` | List all providers | `{ all: `<a href={typesUrl}>Provider[]</a>`, default: {...}, connected: string[] }` |
| `GET` | `/provider/auth` | Get provider authentication methods | `{ [providerID: string]: `<a href={typesUrl}>ProviderAuthMethod[]</a>` }` |
@@ -142,7 +142,7 @@ opencode server izlaže sljedece API-je.
### Sessions
| Method | Path | Description | Notes |
| Metoda | Putanja | Opis | Napomene |
| -------- | ---------------------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------- |
| `GET` | `/session` | List all sessions | Returns <a href={typesUrl}><code>Session[]</code></a> |
| `POST` | `/session` | Create a new session | body: `{ parentID?, title? }`, returns <a href={typesUrl}><code>Session</code></a> |
@@ -167,7 +167,7 @@ opencode server izlaže sljedece API-je.
### Messages
| Method | Path | Description | Notes |
| Metoda | Putanja | Opis | Napomene |
| ------ | --------------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET` | `/session/:id/message` | List messages in a session | query: `limit?`, returns `{ info: `<a href={typesUrl}>Message</a>`, parts: `<a href={typesUrl}>Part[]</a>`}[]` |
| `POST` | `/session/:id/message` | Send a message and wait for response | body: `{ messageID?, model?, agent?, noReply?, system?, tools?, parts }`, returns `{ info: `<a href={typesUrl}>Message</a>`, parts: `<a href={typesUrl}>Part[]</a>`}` |
@@ -180,7 +180,7 @@ opencode server izlaže sljedece API-je.
### Commands
| Method | Path | Description | Response |
| Metoda | Putanja | Opis | Odgovor |
| ------ | ---------- | ----------------- | --------------------------------------------- |
| `GET` | `/command` | List all commands | <a href={typesUrl}><code>Command[]</code></a> |
@@ -188,7 +188,7 @@ opencode server izlaže sljedece API-je.
### Files
| Method | Path | Description | Response |
| Metoda | Putanja | Opis | Odgovor |
| ------ | ------------------------ | ---------------------------------- | ------------------------------------------------------------------------------------------- |
| `GET` | `/find?pattern=<pat>` | Search for text in files | Array of match objects with `path`, `lines`, `line_number`, `absolute_offset`, `submatches` |
| `GET` | `/find/file?query=<q>` | Find files and directories by name | `string[]` (paths) |
@@ -209,7 +209,7 @@ opencode server izlaže sljedece API-je.
### Tools (Experimental)
| Method | Path | Description | Response |
| Metoda | Putanja | Opis | Odgovor |
| ------ | ------------------------------------------- | ---------------------------------------- | -------------------------------------------- |
| `GET` | `/experimental/tool/ids` | List all tool IDs | <a href={typesUrl}><code>ToolIDs</code></a> |
| `GET` | `/experimental/tool?provider=<p>&model=<m>` | List tools with JSON schemas for a model | <a href={typesUrl}><code>ToolList</code></a> |
@@ -218,7 +218,7 @@ opencode server izlaže sljedece API-je.
### LSP, Formatters & MCP
| Method | Path | Description | Response |
| Metoda | Putanja | Opis | Odgovor |
| ------ | ------------ | -------------------------- | -------------------------------------------------------- |
| `GET` | `/lsp` | Get LSP server status | <a href={typesUrl}><code>LSPStatus[]</code></a> |
| `GET` | `/formatter` | Get formatter status | <a href={typesUrl}><code>FormatterStatus[]</code></a> |
@@ -229,7 +229,7 @@ opencode server izlaže sljedece API-je.
### Agents
| Method | Path | Description | Response |
| Metoda | Putanja | Opis | Odgovor |
| ------ | -------- | ------------------------- | ------------------------------------------- |
| `GET` | `/agent` | List all available agents | <a href={typesUrl}><code>Agent[]</code></a> |
@@ -237,15 +237,15 @@ opencode server izlaže sljedece API-je.
### Logging
| Method | Path | Description | Response |
| ------ | ------ | ------------------------------------------------------------ | --------- |
| `POST` | `/log` | Write log entry. Body: `{ service, level, message, extra? }` | `boolean` |
| Metoda | Putanja | Opis | Odgovor |
| ------ | ------- | ------------------------------------------------------------ | --------- |
| `POST` | `/log` | Write log entry. Body: `{ service, level, message, extra? }` | `boolean` |
---
### TUI
| Method | Path | Description | Response |
| Metoda | Putanja | Opis | Odgovor |
| ------ | ----------------------- | ------------------------------------------- | ---------------------- |
| `POST` | `/tui/append-prompt` | Append text to the prompt | `boolean` |
| `POST` | `/tui/open-help` | Open the help dialog | `boolean` |
@@ -263,7 +263,7 @@ opencode server izlaže sljedece API-je.
### Auth
| Method | Path | Description | Response |
| Metoda | Putanja | Opis | Odgovor |
| ------ | ----------- | --------------------------------------------------------------- | --------- |
| `PUT` | `/auth/:id` | Set authentication credentials. Body must match provider schema | `boolean` |
@@ -271,7 +271,7 @@ opencode server izlaže sljedece API-je.
### Events
| Method | Path | Description | Response |
| Metoda | Putanja | Opis | Odgovor |
| ------ | -------- | ----------------------------------------------------------------------------- | ------------------------- |
| `GET` | `/event` | Server-sent events stream. First event is `server.connected`, then bus events | Server-sent events stream |
@@ -279,6 +279,6 @@ opencode server izlaže sljedece API-je.
### Docs
| Method | Path | Description | Response |
| ------ | ------ | ------------------------- | --------------------------- |
| `GET` | `/doc` | OpenAPI 3.1 specification | HTML page with OpenAPI spec |
| Metoda | Putanja | Opis | Odgovor |
| ------ | ------- | ------------------------- | --------------------------- |
| `GET` | `/doc` | OpenAPI 3.1 specification | HTML page with OpenAPI spec |
@@ -7,7 +7,7 @@ Da biste otklonili probleme s OpenCode, počnite provjeravanjem dnevnika i lokal
---
## Dnevnici
## Logovi
Log fajlovi se pišu na:
+7 -7
View File
@@ -292,7 +292,7 @@ Obje naredbe `/editor` i `/export` koriste editor specificiran u vašoj varijabl
<Tabs>
<TabItem label="Linux/macOS">
````bash
```bash
# Example for nano or vim
export EDITOR=nano
export EDITOR=vim
@@ -309,7 +309,7 @@ Obje naredbe `/editor` i `/export` koriste editor specificiran u vašoj varijabl
<TabItem label="Windows (CMD)">
```bash
```bash
set EDITOR=notepad
# For GUI editors, VS Code, Cursor, VSCodium, Windsurf, Zed, etc.
@@ -324,7 +324,7 @@ Obje naredbe `/editor` i `/export` koriste editor specificiran u vašoj varijabl
<TabItem label="Windows (PowerShell)">
```powershell
```powershell
$env:EDITOR = "notepad"
# For GUI editors, VS Code, Cursor, VSCodium, Windsurf, Zed, etc.
@@ -340,13 +340,13 @@ Obje naredbe `/editor` i `/export` koriste editor specificiran u vašoj varijabl
Popularne opcije uređivača uključuju:
- `code` - Visual Studio Code
- `cursor` - Kursor
- `windsurf` - jedrenje na dasci
- `cursor` - Cursor
- `windsurf` - Windsurf
- `nvim` - Neovim editorom
- `vim` - Vim editor
- `nano` - Nano editor
- `notepad` - Windows Notepad
- `subl` - Uzvišeni tekst
- `subl` - Sublime Text
:::note
Neki uređivači kao što je VS Code moraju biti pokrenuti sa `--wait` zastavicom.
@@ -370,7 +370,7 @@ Možete prilagoditi TUI ponašanje putem vašeg OpenCode konfiguracionog fajla.
}
}
}
````
```
### Opcije
+10 -10
View File
@@ -17,7 +17,7 @@ Zen radi kao i svaki drugi provajder u OpenCode. Prijavite se u OpenCode Zen i u
---
## Background
## Pozadina
Postoji veliki broj modela, ali samo mali dio radi dobro kao coding agent. Dodatno, vecina provajdera je drugacije konfigurisana, pa su performanse i kvalitet cesto neujednaceni.
@@ -99,7 +99,7 @@ https://opencode.ai/zen/v1/models
---
## Pricing
## Cijene
Podrzavamo pay-as-you-go model. Ispod su cijene **po 1M tokena**.
@@ -156,7 +156,7 @@ Ako imate pitanja, <a href={email}>kontaktirajte nas</a>.
---
### Auto-reload
### Automatska dopuna
Ako vam stanje padne ispod $5, Zen ce automatski dopuniti $20.
@@ -164,7 +164,7 @@ Iznos auto-reloada mozete promijeniti. Auto-reload mozete i potpuno iskljuciti.
---
### Monthly limits
### Mjesečni limiti
Mozete postaviti mjesecni limit potrosnje za cijeli workspace i za svakog clana tima.
@@ -172,7 +172,7 @@ Na primjer, ako postavite mjesecni limit na $20, Zen nece potrositi vise od $20
---
## Privacy
## Privatnost
Svi nasi modeli su hostovani u SAD-u. Provajderi prate zero-retention politiku i ne koriste vase podatke za treniranje modela, uz sljedece izuzetke:
@@ -185,7 +185,7 @@ Svi nasi modeli su hostovani u SAD-u. Provajderi prate zero-retention politiku i
---
## For Teams
## Za timove
Zen odlicno radi i za timove. Mozete pozvati clanove tima, dodijeliti uloge, birati modele koje tim koristi i jos mnogo toga.
@@ -197,7 +197,7 @@ Upravljanje workspace-om je trenutno besplatno za timove tokom beta faze. Vise d
---
### Roles
### Uloge
Mozete pozvati clanove tima u workspace i dodijeliti uloge:
@@ -208,7 +208,7 @@ Admini mogu postaviti i mjesecne limite potrosnje po clanu da drze troskove pod
---
### Model access
### Pristup modelima
Admini mogu ukljuciti ili iskljuciti odredene modele za workspace. Zahtjevi prema iskljucenom modelu vracaju gresku.
@@ -216,7 +216,7 @@ Ovo je korisno kada zelite zabraniti model koji prikuplja podatke.
---
### Bring your own key
### Donesite vlastiti ključ
Mozete koristiti vlastite OpenAI ili Anthropic API kljuceve i dalje koristiti ostale modele u Zen-u.
@@ -226,7 +226,7 @@ Na primjer, vasa organizacija mozda vec ima OpenAI ili Anthropic kljuc i zelite
---
## Goals
## Ciljevi
OpenCode Zen smo napravili da:
+3 -1
View File
@@ -490,13 +490,15 @@ You can control context compaction behavior through the `compaction` option.
"$schema": "https://opencode.ai/config.json",
"compaction": {
"auto": true,
"prune": true
"prune": true,
"reserved": 10000
}
}
```
- `auto` - Automatically compact the session when context is full (default: `true`).
- `prune` - Remove old tool outputs to save tokens (default: `true`).
- `reserved` - Token buffer for compaction. Leaves enough window to avoid overflow during compaction
---
+3 -3
View File
@@ -6,7 +6,7 @@ description: Brug OpenCode i enhver ACP-kompatibel editor.
OpenCode understøtter [Agent Client Protocol](https://agentclientprotocol.com) eller (ACP), så du kan bruge det direkte i kompatible editorer og IDE'er.
:::tip
For en liste over editorer og værktøjer, der understøtter ACP, tjek [ACP progress report](https://zed.dev/blog/acp-progress-report#available-now).
For en liste over editorer og værktøjer, der understøtter ACP, tjek [ACP fremskridtsrapport](https://zed.dev/blog/acp-progress-report#available-now).
:::
ACP er en åben protokol, der standardiserer kommunikation mellem kodeeditorer og AI-kodningsagenter.
@@ -69,7 +69,7 @@ Du kan også binde en tastaturgenvej ved at redigere din `keymap.json`:
### JetBrains IDE'er
Tilføj til din [JetBrains IDE](https://www.jetbrains.com/) acp.json i henhold til [documentation](https://www.jetbrains.com/help/ai-assistant/acp.html):
Tilføj til din [JetBrains IDE](https://www.jetbrains.com/) acp.json i henhold til [dokumentationen](https://www.jetbrains.com/help/ai-assistant/acp.html):
```json title="acp.json"
{
@@ -138,7 +138,7 @@ require("codecompanion").setup({
Denne konfiguration sætter CodeCompanion til at bruge OpenCode som ACP-agent til chat.
Hvis du har brug for at sende miljøvariabler (som `OPENCODE_API_KEY`), henvises til [Configuring Adapters: Environment Variables](https://codecompanion.olimorris.dev/getting-started#setting-an-api-key) i CodeCompanion.nvim-dokumentationen for alle detaljer.
Hvis du har brug for at sende miljøvariabler (som `OPENCODE_API_KEY`), henvises til [Konfiguration af adaptere: Miljøvariabler](https://codecompanion.olimorris.dev/getting-started#setting-an-api-key) i CodeCompanion.nvim-dokumentationen for alle detaljer.
## Support
+8 -8
View File
@@ -27,7 +27,7 @@ Primære agenter er de vigtigste assistenter, du interagerer direkte med. Du kan
Du kan bruge **Tab**-tasten til at skifte mellem primære agenter under en session.
:::
OpenCode leveres med indbyggede primære agenter, **Build** og **Plan**. Godt
OpenCode leveres med indbyggede primære agenter, **Build** og **Plan**. Vi vil
se på disse nedenfor.
---
@@ -36,7 +36,7 @@ se på disse nedenfor.
Subagenter er specialiserede assistenter, som primære agenter kan påbegynde sig til specifikke opgaver. Du kan også kalde dem manuelt ved at **@ nævne** dem i dine beskeder.
OpenCode leveres med til indbyggede underagenter, **Generelt** og **Udforsk**. Vi vil se på dette nedenfor.
OpenCode leveres med to indbyggede underagenter, **Generelt** og **Udforsk**. Vi vil se på dette nedenfor.
---
@@ -136,7 +136,7 @@ Du kan tilpasse de indbyggede agenter eller oprette dine egne gennem konfigurati
### JSON
Konfigurationsagent i din `opencode.json`-konfigurationsfil:
Konfigurer agent i din `opencode.json`-konfigurationsfil:
```json title="opencode.json"
{
@@ -302,7 +302,7 @@ Hvis dette ikke er indstillet, vil agenten fortsætte med at iterere, indtil mod
Når grænsen er nået, modtager agenten en speciel systemprompt, der instruerer den om at svare med en opsummering af sit arbejde og anbefalede resterende opgaver.
:::caution
Det gamle `maxSteps`lte er forældet. Brug `steps` i stedet.
Det gamle `maxSteps`-felt er forældet. Brug `steps` i stedet.
:::
---
@@ -346,7 +346,7 @@ Denne sti er i forhold til, hvor konfigurationsfilen er placeret. Så dette virk
Brug `model`-konfigurationen til at tilsidesætte modeller for denne agent. Nyttigt til brug af forskellige modeller optimeret til forskellige opgaver. For eksempel en hurtigere model til planlægning, en dygtig model til implementering.
:::tip
Hvis du ikke angiver en model, bruger primære agenter [model globally configured](/docs/config#models), mens subagenter vil bruge modeller for den primære agent, der påkaldte subagenten.
Hvis du ikke angiver en model, bruger primære agenter [model konfigureret globalt](/docs/config#models), mens subagenter vil bruge modeller for den primære agent, der påkaldte subagenten.
:::
```json title="opencode.json"
@@ -406,7 +406,7 @@ Du kan også bruge jokertegn til at styre flere værktøjer på én gang. For ek
}
```
[Learn more about tools](/docs/tools).
[Læs mere om værktøjer](/docs/tools).
---
@@ -518,7 +518,7 @@ Da den sidste matchningsregel har forrang, skal du sætte jokertegnet `*` først
}
```
[Learn more about permissions](/docs/permissions).
[Læs mere om tilladelser](/docs/permissions).
---
@@ -697,7 +697,7 @@ Her er nogle almindelige use cases for forskellige agenter.
Her er nogle eksempler på agenter, du kan finde nyttige.
:::tip
Har du en agent, du gerne vil dele? [Submit a PR](https://github.com/anomalyco/opencode).
Har du en agent, du gerne vil dele? [Send en PR](https://github.com/anomalyco/opencode).
:::
---
+3 -3
View File
@@ -9,7 +9,7 @@ export const console = config.console
[**OpenCode**](/) er en open source AI-kodningsagent. Den er tilgængelig som en terminalbaseret grænseflade, desktop-app eller IDE-udvidelse.
![OpenCode TUI with the opencode theme](../../../assets/lander/screenshot.png)
![OpenCode TUI med opencode-temaet](../../../assets/lander/screenshot.png)
Lad os komme i gang.
@@ -228,7 +228,7 @@ Du kan bede OpenCode om at tilføje nye funktioner til dit projekt. Selvom vi f
Skift til det ved hjælp af **Tab**-tasten. Du vil se en indikator for dette i nederste højre hjørne.
```bash frame="none" title="Switch to Plan mode"
```bash frame="none" title="Skift til Plan-tilstand"
<TAB>
```
@@ -356,4 +356,4 @@ Her er en [eksempelsamtale](https://opencode.ai/s/4XP1fce5) med OpenCode.
Og det er det! Du er nu en professionel i at bruge OpenCode.
For at gøre det til dit eget, anbefaler vi [picking a theme](/docs/themes), [customizing the keybinds](/docs/keybinds), [configuring code formatters](/docs/formatters), [creating custom commands](/docs/commands) eller at lege med [OpenCode config](/docs/config).
For at gøre det til dit eget, anbefaler vi [at vælge et tema](/docs/themes), [tilpasse genvejstaster](/docs/keybinds), [konfigurere kodeformater](/docs/formatters), [oprette brugerdefinerede kommandoer](/docs/commands) eller at lege med [OpenCode-konfiguration](/docs/config).
+56 -56
View File
@@ -1,25 +1,25 @@
---
title: TUI
description: Bruge OpenCode-terminalbrugergrensesnittet.
description: Bruge OpenCode-terminalbrugergrænsefladeet.
---
import { Tabs, TabItem } from "@astrojs/starlight/components"
OpenCode gir et interaktivt terminalgrensesnitt eller TUI for at jobbe med prosjektene dine med en LLM.
OpenCode gir et interaktivt terminalgrænseflade eller TUI for at jobbe med prosjektene dine med en LLM.
Å kjøre OpenCode starter TUI for nuværende katalog.
Å køre OpenCode starter TUI for nuværende mappe.
```bash
opencode
```
Eller du kan starte den for en bestemt arbejdskatalog.
Eller du kan starte den for en bestemt arbejdsmappe.
```bash
opencode /path/to/project
```
Når du er i TUI, kan du spørre den med en melding.
Når du er i TUI, kan du spørge den med en besked.
```text
Give me a quick summary of the codebase.
@@ -27,9 +27,9 @@ Give me a quick summary of the codebase.
---
## Filreferanser
## Filreferencer
Du kan referere til filer i dine meddelelser med `@`. Det laver en fuzzy filsøgning i det nuværende arbejdskatalog.
Du kan referere til filer i dine meddelelser med `@`. Det laver en fuzzy filsøgning i det nuværende arbejdsmappe.
:::tip
Du kan også bruge `@` til at referere til filer i dine meddelelser.
@@ -39,19 +39,19 @@ Du kan også bruge `@` til at referere til filer i dine meddelelser.
How is auth handled in @packages/functions/src/api/index.ts?
```
Innholdet i filen tilføjes automatisk til samtalen.
Indholdet i filen tilføjes automatisk til samtalen.
---
## Bash-kommandoer
Start en melding med `!` for at kjøre en shell-kommando.
Start en besked med `!` for at køre en shell-kommando.
```bash frame="none"
!ls -la
```
Utdataene fra kommandoen tilføjes til samtalen som et verktøyresultat.
Outputtet fra kommandoen tilføjes til samtalen som et værktøjresultat.
---
@@ -63,15 +63,15 @@ Når du bruger OpenCode TUI, kan du skrive `/` etterfulgt av et kommandonavn for
/help
```
De fleste kommandoer har også tastebinding som bruger `ctrl+x` som Leader-tast, der `ctrl+x` er standard Leader-tast. [Finn ut mer](/docs/keybinds).
De fleste kommandoer har også genvejstast som bruger `ctrl+x` som Leader-tast, der `ctrl+x` er standard Leader-tast. [Læs mere](/docs/keybinds).
Her er alle tilgængelige skråstrekkommandoer:
Her er alle tilgængelige skråstregskommandoer:
---
### connect
Legg til en leverandør til OpenCode. Lar deg velge fra tilgængelige leverandører og tilføje til deres API-nøkler.
Tilføj en leverandør til OpenCode. Lar deg velge fra tilgængelige leverandører og tilføje til deres API-nøgler.
```bash frame="none"
/connect
@@ -87,79 +87,79 @@ Komprimer nuværende session. _Alias_: `/summarize`
/compact
```
**Tastebinding:** `ctrl+x c`
**Genvejstast:** `ctrl+x c`
---
### details
Veksle verktøyutførelsesdetaljer.
Veksle værktøjutførelsesdetaljer.
```bash frame="none"
/details
```
**Tastebinding:** `ctrl+x d`
**Genvejstast:** `ctrl+x d`
---
### editor
Åpne eksternt redigeringsprogram for at skrive meldinger. Bruger redigeringssettet i miljøvariabelen `EDITOR`. [Finn ut mer](#editor-setup).
Åbn eksternt redigeringsprogram for at skrive beskeder. Bruger redigeringsprogrammet i miljøvariabelen `EDITOR`. [Læs mere](#editor-setup).
```bash frame="none"
/editor
```
**Tastebinding:** `ctrl+x e`
**Genvejstast:** `ctrl+x e`
---
### exit
Avslutt OpenCode. _Aliaser_: `/quit`, `/q`
Afslut OpenCode. _Aliaser_: `/quit`, `/q`
```bash frame="none"
/exit
```
**Tastebinding:** `ctrl+x q`
**Genvejstast:** `ctrl+x q`
---
### export
Eksporter nuværende samtale til Markdown og åpne i standardredigeringsprogrammet. Bruger redigeringssettet i miljøvariabelen `EDITOR`. [Finn ut mer](#editor-setup).
Eksporter nuværende samtale til Markdown og åpne i standardredigeringsprogrammet. Bruger redigeringsprogrammet i miljøvariabelen `EDITOR`. [Læs mere](#editor-setup).
```bash frame="none"
/export
```
**Tastebinding:** `ctrl+x x`
**Genvejstast:** `ctrl+x x`
---
### help
Vis hjelpedialogen.
Vis hjælpedialogen.
```bash frame="none"
/help
```
**Tastebinding:** `ctrl+x h`
**Genvejstast:** `ctrl+x h`
---
### init
Opret eller opdater `AGENTS.md`-fil. [Finn ut mer](/docs/rules).
Opret eller opdater `AGENTS.md`-fil. [Læs mere](/docs/rules).
```bash frame="none"
/init
```
**Tastebinding:** `ctrl+x i`
**Genvejstast:** `ctrl+x i`
---
@@ -171,7 +171,7 @@ Liste over tilgængelige modeller.
/models
```
**Tastebinding:** `ctrl+x m`
**Genvejstast:** `ctrl+x m`
---
@@ -183,26 +183,26 @@ Start en ny session. _Alias_: `/clear`
/new
```
**Tastebinding:** `ctrl+x n`
**Genvejstast:** `ctrl+x n`
---
### redo
Gjenta en tidligere angret melding. Kun tilgængelig etter brug av `/undo`.
Gentag en tidligere fortrudt besked. Kun tilgængelig etter brug av `/undo`.
:::tip
Eventuelle filendringer vil også bli gjenopretet.
Eventuelle filændringer vil også bli genoprettet.
:::
Internt bruger dette Git til at administrere filendringene. Så ditt prosjekt **må
Internt bruger dette Git til at administrere filændringerne. Så dit prosjekt **må
være et Git-depot**.
```bash frame="none"
/redo
```
**Tastebinding:** `ctrl+x r`
**Genvejstast:** `ctrl+x r`
---
@@ -214,19 +214,19 @@ List opp og bytt mellom sessioner. _Aliaser_: `/resume`, `/continue`
/sessions
```
**Tastebinding:** `ctrl+x l`
**Genvejstast:** `ctrl+x l`
---
### share
Del nuværende session. [Finn ut mer](/docs/share).
Del nuværende session. [Læs mere](/docs/share).
```bash frame="none"
/share
```
**Tastebinding:** `ctrl+x s`
**Genvejstast:** `ctrl+x s`
---
@@ -238,16 +238,16 @@ Liste over tilgængelige temaer.
/theme
```
**Tastebinding:** `ctrl+x t`
**Genvejstast:** `ctrl+x t`
---
### thinking
Veksle mellom synligheten av tenke-/resonneringsblokker i samtalen. Når den er aktivert, kan du se modellens resonneringsprosess for modeller som støtter utvidet tenkning.
Veksle mellom synligheten av tenke-/resonneringsblokker i samtalen. Når den er aktiveret, kan du se modellens resonneringsprosess for modeller som støtter utvidet tenkning.
:::note
Denne kommandoen kontrollerer bare om tenkeblokker **vises** - den aktiverer eller deaktiverer ikke modellens resonneringsmuligheter. For at veksle mellom faktiske resonneringsmuligheter, brug `ctrl+t` for at bla gjennom modellvarianter.
Denne kommandoen kontrollerer bare om tenkeblokker **vises** - den aktiverer eller deaktiverer ikke modellens resonneringsmuligheter. For at veksle mellom faktiske resonneringsmuligheter, brug `ctrl+t` for at bla gennem modellvarianter.
:::
```bash frame="none"
@@ -258,26 +258,26 @@ Denne kommandoen kontrollerer bare om tenkeblokker **vises** - den aktiverer ell
### undo
Angre siste melding i samtalen. Fjerner den siste brugermeldingen, alle påfølgende svar og eventuelle filendringer.
Angre siste besked i samtalen. Fjerner den siste brugerbeskeden, alle påfølgende svar og eventuelle filændringer.
:::tip
Eventuelle filendringer vil også bli nulstillet.
Eventuelle filændringer vil også bli nullstillet.
:::
Internt bruger dette Git til at administrere filendringene. Så ditt prosjekt **må
Internt bruger dette Git til at administrere filændringerne. Så dit prosjekt **må
være et Git-depot**.
```bash frame="none"
/undo
```
**Tastebinding:** `ctrl+x u`
**Genvejstast:** `ctrl+x u`
---
### unshare
Opphev deling av nuværende session. [Finn ut mer](/docs/share#un-sharing).
Opv deling av nuværende session. [Læs mere](/docs/share#un-sharing).
```bash frame="none"
/unshare
@@ -287,7 +287,7 @@ Opphev deling av nuværende session. [Finn ut mer](/docs/share#un-sharing).
## Opsætning af editor
Både kommandoene `/editor` og `/export` bruger editoren som er spesifisert i miljøvariabelen `EDITOR`.
Både kommandoerne `/editor` og `/export` bruger editoren som er spesifisert i miljøvariabelen `EDITOR`.
<Tabs>
<TabItem label="Linux/macOS">
@@ -315,7 +315,7 @@ Både kommandoene `/editor` og `/export` bruger editoren som er spesifisert i mi
set EDITOR=code --wait
```
For at gøre det permanent, brug **Systemegenskaper** > **Miljø
For at gøre det permanent, brug **Systemegenskaber** > **Miljø
Variabler**.
</TabItem>
@@ -339,23 +339,23 @@ Populære redigeringsalternativer inkluderer:
- `code` - Visual Studio Code
- `cursor` - Cursor
- `windsurf` - Windsurf
- `nvim` - Neovim-redaktør
- `nvim` - Neovim-editor
- `vim` - Vim editor
- `nano` - Nano-editor
- `notepad` - Windows Notesblok
- `subl` - Sublime Text
:::note
Nogle editorer som VS Code må startes med flagget `--wait`.
Nogle editorer som VS Code må startes med flaget `--wait`.
:::
Nogle editorer trenger kommandolinjeargumenter for at kjøre i blokeringstilstand. `--wait`-flagget gør at redigeringsprosessen blokkeres til den lukkes.
Nogle editorer kræver kommandolinjeargumenter for at køre i blokeringstilstand. `--wait`-flaget gør at redigeringsprocessen blokeres til den lukkes.
---
## Konfigurer
Du kan tilpasse TUI-oppførselen gjennom OpenCode-konfigurasjonsfilen.
Du kan tilpasse TUI-adfærden gennem OpenCode-konfigurationsfilen.
```json title="opencode.json"
{
@@ -369,22 +369,22 @@ Du kan tilpasse TUI-oppførselen gjennom OpenCode-konfigurasjonsfilen.
}
```
### Options
### Indstillinger
- `scroll_acceleration` - Aktiver rulleacceleration i macOS-stil for jevn, naturlig rulling. Når aktivert, øker rullehastigheten med raske rullebevegelser og forblir presis for langsommere bevegelser. **Denne innstillingen har forrang over `scroll_speed` og overstyrer den når den er aktivert.**
- `scroll_speed` - Styrer hvor raskt TUI ruller når du bruger rullekommandoer (minimum: `1`). Standard er `3`. **Merk: Dette ignoreres hvis `scroll_acceleration.enabled` er satt til `true`.**
- `scroll_acceleration` - Aktiver rulleacceleration i macOS-stil for jævn, naturlig rulning. Når aktiveret, øger rullehastigheden med raske rullebevægelser og forblir presis for langsommere bevægelser. **Denne indstillingen har forrang over `scroll_speed` og tilsidesætter den når den er aktiveret.**
- `scroll_speed` - Styrer hvor raskt TUI ruller når du bruger rullekommandoer (minimum: `1`). Standard er `3`. **Bemærk: Dette ignoreres hvis `scroll_acceleration.enabled` er sat til `true`.**
---
## Tilpasning
Du kan tilpasse ulike aspekter av TUI-visningen ved at bruge kommandopaletten (`ctrl+x h` eller `/help`). Disse innstillingene vedvarer ved omstarter.
Du kan tilpasse forskellige aspekter av TUI-visningen ved at bruge kommandopaletten (`ctrl+x h` eller `/help`). Disse indstillingene gemmes ved omstarter.
---
#### Username display
Veksle om brugernavnet ditt vises i chat-meldinger. Få tilgang til dette gjennom:
Veksle om brugernavnet dit vises i chat-beskeder. Få adgang til dette gennem:
- Kommandopalet: Søk etter "brugernavn" eller "skjul brugernavn"
- Innstillingen vedvarer automatisk og vil bli husket over TUI sessioner
- Kommandopalet: Søg efter "brugernavn" eller "skjul brugernavn"
- Innstillingen gemmes automatisk og vil bli husket over TUI sessioner
+4 -4
View File
@@ -359,7 +359,7 @@ Wenn Sie kein Modell angeben, verwenden Sie den primären Agenten [model globall
}
```
Das Modell ID in Ihrer OpenCode-Konfiguration verwendet das Format `provider/model-id`. Wenn Sie beispielsweise [OpenCode Zen](/docs/zen) verwenden, würden Sie `opencode/gpt-5.1-codex` für GPT 5.1 Codex verwenden.
Die Modell-ID in Ihrer OpenCode-Konfiguration verwendet das Format `provider/model-id`. Wenn Sie beispielsweise [OpenCode Zen](/docs/zen) verwenden, würden Sie `opencode/gpt-5.1-codex` für GPT 5.1 Codex verwenden.
---
@@ -591,7 +591,7 @@ Die Regeln werden der Reihe nach ausgewertet und die **letzte übereinstimmende
:::
:::tip
Benutzer können jeden Subagenten immer direkt über das Autovervollständigungsmenü `@` aufrufen, auch wenn die Aufgabenberechtigungen des Agenten sterben würden.
Benutzer können jeden Subagenten immer direkt über das Autovervollständigungsmenü `@` aufrufen, auch wenn die Aufgabenberechtigungen des Agenten dies verweigern würden.
:::
---
@@ -600,7 +600,7 @@ Benutzer können jeden Subagenten immer direkt über das Autovervollständigungs
Passen Sie das visuelle Erscheinungsbild des Agenten im UI mit der Option `color` an. Dies wirkt sich darauf aus, wie der Agent in der Benutzeroberfläche angezeigt wird.
Verwenden Sie eine gültige Hex-Farbe (e.g., `#FF5733`) oder Designfarbe: `primary`, `secondary`, `accent`, `success`, `warning`, `error`, `info`.
Verwenden Sie eine gültige Hex-Farbe (e.g., `#FF5733`) oder Themenfarbe: `primary`, `secondary`, `accent`, `success`, `warning`, `error`, `info`.
```json title="opencode.json"
{
@@ -631,7 +631,7 @@ Steuern Sie die Antwortvielfalt mit der Option `top_p`. Alternative zur Temperat
}
```
Die Werte reichen von 0.0 bis 1.0. Die meisten Werte sind fokussierter, die höheren Werte vielfältiger.
Die Werte reichen von 0.0 bis 1.0. Niedrigere Werte sind fokussierter, höhere Werte vielfältiger.
---
+8 -8
View File
@@ -3,7 +3,7 @@ title: IDE
description: Die OpenCode-Erweiterung für VS Code, Cursor und andere IDEs
---
OpenCode lässt sich in VS Code, Cursor oder jeden IDE integrieren, der ein Terminal unterstützt. Führen Sie einfach `opencode` im Terminal aus, um zu beginnen.
OpenCode lässt sich in VS Code, Cursor oder jede IDE integrieren, die ein Terminal unterstützt. Führen Sie einfach `opencode` im Terminal aus, um zu beginnen.
---
@@ -18,13 +18,13 @@ OpenCode lässt sich in VS Code, Cursor oder jeden IDE integrieren, der ein Term
## Installation
So installieren Sie OpenCode auf VS Code und beliebten Forks wie Cursor, Windsurf, VSCodium:
So installieren Sie OpenCode in VS Code und beliebten Forks wie Cursor, Windsurf, VSCodium:
1. Öffnen Sie den VS-Code
1. Öffnen Sie VS Code
2. Öffnen Sie das integrierte Terminal
3. Führen Sie `opencode` aus die Erweiterung wird automatisch installiert
Wenn Sie andererseits Ihr eigenes IDE verwenden möchten, wenn Sie `/editor` oder `/export` von TUI ausführen, müssen Sie `export EDITOR="code --wait"` festlegen. [Learn more](/docs/tui/#editor-setup).
Wenn Sie andererseits Ihre eigene IDE verwenden möchten, wenn Sie `/editor` oder `/export` von TUI ausführen, müssen Sie `export EDITOR="code --wait"` festlegen. [Learn more](/docs/tui/#editor-setup).
---
@@ -39,10 +39,10 @@ Suchen Sie im Extension Marketplace nach **OpenCode** und klicken Sie auf **Inst
Wenn die Erweiterung nicht automatisch installiert werden kann:
- Stellen Sie sicher, dass Sie `opencode` im integrierten Terminal ausführen.
- Bestätigen Sie, dass CLI für Ihr IDE installiert ist:
- Bestätigen Sie, dass CLI für Ihre IDE installiert ist:
- Für VS Code: `code` Befehl
- Für Cursor: Befehl `cursor`
- Für Windsurfen: Befehl `windsurf`
- Für Windsurf: Befehl `windsurf`
- Für VSCodium: Befehl `codium`
- Wenn nicht, führen Sie `Cmd+Shift+P` (Mac) oder `Ctrl+Shift+P` (Windows/Linux) aus und suchen Sie nach „Shell Command: Install 'code' command in PATH“ (oder dem Äquivalent für Ihr IDE).
- Stellen Sie sicher, dass der VS-Code über die Berechtigung zum Installieren von Erweiterungen verfügt
- Wenn nicht, führen Sie `Cmd+Shift+P` (Mac) oder `Ctrl+Shift+P` (Windows/Linux) aus und suchen Sie nach „Shell Command: Install 'code' command in PATH“ (oder dem Äquivalent für Ihre IDE).
- Stellen Sie sicher, dass VS Code über die Berechtigung zum Installieren von Erweiterungen verfügt
+4 -6
View File
@@ -79,7 +79,7 @@ Sie können es auch mit den folgenden Befehlen installieren:
brew install anomalyco/tap/opencode
```
> Wir empfehlen die Verwendung des OpenCode-Tapes für die aktuellsten Versionen. Die offizielle `brew install opencode`-Formel wird vom Homebrew-Team gepflegt und seltener aktualisiert.
> Wir empfehlen die Verwendung des OpenCode-Taps für die aktuellsten Versionen. Die offizielle `brew install opencode`-Formel wird vom Homebrew-Team gepflegt und seltener aktualisiert.
- **Verwendung von Paru unter Arch Linux**
@@ -93,7 +93,7 @@ Sie können es auch mit den folgenden Befehlen installieren:
Für das beste Erlebnis unter Windows empfehlen wir die Verwendung von [Windows Subsystem for Linux (WSL)](/docs/windows-wsl). Es bietet eine bessere Leistung und vollständige Kompatibilität mit den Funktionen von OpenCode.
:::
- **Verwendung von Schokolade**
- **Verwendung von Chocolatey**
```bash
choco install opencode
@@ -134,8 +134,7 @@ Sie können die Binärdatei auch aus [Releases](https://github.com/anomalyco/ope
Mit OpenCode können Sie jeden LLM-Anbieter verwenden, indem Sie dessen API-Schlüssel konfigurieren.
Wenn Sie mit der Verwendung von LLM-Anbietern noch nicht vertraut sind, empfehlen wir die Verwendung von [OpenCode Zen](/docs/zen).
Es handelt sich um eine kuratierte Liste von Modellen, die von OpenCode getestet und verifiziert wurden
team.
Es handelt sich um eine kuratierte Liste von Modellen, die vom OpenCode Team getestet und verifiziert wurden.
1. Führen Sie den Befehl `/connect` in TUI aus, wählen Sie OpenCode aus und gehen Sie zu [opencode.ai/auth](https://opencode.ai/auth).
@@ -179,8 +178,7 @@ Als nächstes initialisieren Sie OpenCode für das Projekt, indem Sie den folgen
/init
```
Dadurch wird OpenCode dazu veranlasst, Ihr Projekt zu analysieren und eine `AGENTS.md`-Datei zu erstellen
das Projektstammverzeichnis.
Dadurch wird OpenCode dazu veranlasst, Ihr Projekt zu analysieren und eine `AGENTS.md`-Datei im Projektstammverzeichnis zu erstellen.
:::tip
Sie sollten die `AGENTS.md`-Datei Ihres Projekts an Git übergeben.
+1 -1
View File
@@ -3,7 +3,7 @@ title: Soporte ACP
description: Utilice OpenCode en cualquier editor compatible con ACP.
---
OpenCode admite el [Protocolo de cliente de agente](https://agentclientprotocol.com) o (ACP), lo que le permite usarlo directamente en editores e IDE compatibles.
OpenCode admite el [Agent Client Protocol](https://agentclientprotocol.com) o (ACP), lo que le permite usarlo directamente en editores e IDE compatibles.
:::tip
Para obtener una lista de editores y herramientas compatibles con ACP, consulte el [informe de progreso de ACP](https://zed.dev/blog/acp-progress-report#available-now).
+7 -7
View File
@@ -471,7 +471,7 @@ Esto inicia un servidor HTTP y abre un navegador web para acceder a OpenCode a t
### acp
Inicie un servidor ACP (Protocolo de cliente de agente).
Inicie un servidor ACP (Agent Client Protocol).
```bash
opencode acp
@@ -540,12 +540,12 @@ opencode upgrade v0.1.48
El opencode CLI toma las siguientes banderas globales.
| Bandera | Corto | Descripción |
| -------------- | ----- | --------------------------------------------------------------- |
| `--help` | `-h` | Mostrar ayuda |
| `--version` | `-v` | Número de versión de impresión |
| `--print-logs` | | Imprimir registros en stderr |
| `--log-level` | | Nivel de registro (DEPURACIÓN, INFORMACIÓN, ADVERTENCIA, ERROR) |
| Bandera | Corto | Descripción |
| -------------- | ----- | -------------------------------------------- |
| `--help` | `-h` | Mostrar ayuda |
| `--version` | `-v` | Número de versión de impresión |
| `--print-logs` | | Imprimir registros en stderr |
| `--log-level` | | Nivel de registro (DEBUG, INFO, WARN, ERROR) |
---
+5 -5
View File
@@ -22,8 +22,8 @@ Para usar OpenCode en su terminal, necesitará:
1. Un emulador de terminal moderno como:
- [WezTerm](https://wezterm.org), multiplataforma
- [Alacritty](https://alacritty.org), multiplataforma
- [Fantasmal](https://ghostty.org), Linux y macOS
- [Gatito](https://sw.kovidgoyal.net/kitty/), Linux y macOS
- [Ghostty](https://ghostty.org), Linux y macOS
- [Kitty](https://sw.kovidgoyal.net/kitty/), Linux y macOS
2. API claves para los LLM proveedores que desea utilizar.
@@ -79,7 +79,7 @@ También puedes instalarlo con los siguientes comandos:
brew install anomalyco/tap/opencode
```
> Recomendamos utilizar el grifo OpenCode para las versiones más actualizadas. La fórmula oficial `brew install opencode` la mantiene el equipo Homebrew y se actualiza con menos frecuencia.
> Recomendamos utilizar el tap OpenCode para las versiones más actualizadas. La fórmula oficial `brew install opencode` la mantiene el equipo Homebrew y se actualiza con menos frecuencia.
- **Usando Paru en Arch Linux**
@@ -223,7 +223,7 @@ Puede pedirle a OpenCode que agregue nuevas funciones a su proyecto. Aunque prim
1. **Crea un plan**
OpenCode tiene un _modo Plan_ que desactiva su capacidad para realizar cambios y
OpenCode tiene un _Plan mode_ que desactiva su capacidad para realizar cambios y
en su lugar, sugiera _cómo_ implementará la función.
Cambie a él usando la tecla **Tab**. Verás un indicador para esto en la esquina inferior derecha.
@@ -266,7 +266,7 @@ Puede pedirle a OpenCode que agregue nuevas funciones a su proyecto. Aunque prim
3. **Crea la función**
Una vez que se sienta cómodo con el plan, vuelva al _modo de construcción_
Una vez que se sienta cómodo con el plan, vuelva al _Build mode_
presionando la tecla **Tab** nuevamente.
```bash frame="none"
+18 -18
View File
@@ -14,35 +14,35 @@ OpenCode viene con varios servidores LSP integrados para idiomas populares:
| LSP Servidor | Extensiones | Requisitos |
| ------------------ | ------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| astro | .astro | Autoinstalaciones para proyectos Astro |
| fiesta | .sh, .bash, .zsh, .ksh | Autoinstala el servidor en lenguaje bash |
| sonido metálico | .c, .cpp, .cc, .cxx, .c++, .h, .hpp, .hh, .hxx, .h++ | Instalaciones automáticas para proyectos C/C++ |
| csostenido | .cs | `.NET SDK` instalado |
| bash | .sh, .bash, .zsh, .ksh | Autoinstala el servidor en lenguaje bash |
| clangd | .c, .cpp, .cc, .cxx, .c++, .h, .hpp, .hh, .hxx, .h++ | Instalaciones automáticas para proyectos C/C++ |
| csharp | .cs | `.NET SDK` instalado |
| clojure-lsp | .clj, .cljs, .cljc, .edn | Comando `clojure-lsp` disponible |
| dardo | .dardo | Comando `dart` disponible |
| dart | .dart | Comando `dart` disponible |
| deno | .ts, .tsx, .js, .jsx, .mjs | Comando `deno` disponible (detecta automáticamente deno.json/deno.jsonc) |
| elixir-ls | .ex, .exs | Comando `elixir` disponible |
| eslint | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts, .vue | `eslint` dependencia en proyecto |
| f sostenido | .fs, .fsi, .fsx, .fsscript | `.NET SDK` instalado |
| brillo | .brillo | Comando `gleam` disponible |
| gopls | .ir | Comando `go` disponible |
| fsharp | .fs, .fsi, .fsx, .fsscript | `.NET SDK` instalado |
| gleam | .gleam | Comando `gleam` disponible |
| gopls | .go | Comando `go` disponible |
| hls | .hs, .lhs | Comando `haskell-language-server-wrapper` disponible |
| jdtls | .java | `Java SDK (version 21+)` instalado |
| kotlin-ls | .kt, .kts | Autoinstalaciones para proyectos Kotlin |
| lua-ls | .lua | Autoinstalaciones para proyectos Lua |
| nada | .nix | Comando `nixd` disponible |
| nixd | .nix | Comando `nixd` disponible |
| ocaml-lsp | .ml, .mli | Comando `ocamllsp` disponible |
| oxlint | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts, .vue, .astro, .svelte | `oxlint` dependencia en proyecto |
| php intelefense | .php | Autoinstalaciones para proyectos PHP |
| php intelephense | .php | Autoinstalaciones para proyectos PHP |
| prisma | .prisma | Comando `prisma` disponible |
| derechos de autor | .py, .pyi | Dependencia `pyright` instalada |
| rubí-lsp (rubocop) | .rb, .rake, .gemspec, .ru | Comandos `ruby` y `gem` disponibles |
| óxido | .rs | Comando `rust-analyzer` disponible |
| kit de fuente-lsp | .swift, .objc, .objcpp | `swift` instalado (`xcode` en macOS) |
| esbelto | .esbelto | Autoinstalaciones para proyectos Svelte |
| terraformar | .tf, .tfvars | Instalaciones automáticas desde versiones GitHub |
| diminuto | .tipo, .tipo | Instalaciones automáticas desde versiones GitHub |
| mecanografiado | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts | `typescript` dependencia en proyecto |
| vista | .vue | Autoinstalaciones para proyectos Vue |
| pyright | .py, .pyi | Dependencia `pyright` instalada |
| ruby-lsp (rubocop) | .rb, .rake, .gemspec, .ru | Comandos `ruby` y `gem` disponibles |
| rust | .rs | Comando `rust-analyzer` disponible |
| sourcekit-lsp | .swift, .objc, .objcpp | `swift` instalado (`xcode` en macOS) |
| svelte | .svelte | Autoinstalaciones para proyectos Svelte |
| terraform | .tf, .tfvars | Instalaciones automáticas desde versiones GitHub |
| tinymist | .typ, .typc | Instalaciones automáticas desde versiones GitHub |
| typescript | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts | `typescript` dependencia en proyecto |
| vue | .vue | Autoinstalaciones para proyectos Vue |
| yaml-ls | .yaml, .yml | Autoinstala Red Hat yaml-language-server |
| zls | .zig, .zon | Comando `zig` disponible |
+13 -13
View File
@@ -45,7 +45,7 @@ Aquí están todas las herramientas integradas disponibles en OpenCode.
---
### fiesta
### bash
Ejecute comandos de shell en el entorno de su proyecto.
@@ -62,7 +62,7 @@ Esta herramienta permite que LLM ejecute comandos de terminal como `npm install`
---
### editar
### edit
Modifique archivos existentes utilizando reemplazos de cadenas exactas.
@@ -79,7 +79,7 @@ Esta herramienta realiza ediciones precisas de archivos reemplazando coincidenci
---
### escribir
### write
Cree nuevos archivos o sobrescriba los existentes.
@@ -100,7 +100,7 @@ La herramienta `write` está controlada por el permiso `edit`, que cubre todas l
---
### leer
### read
Lea el contenido del archivo desde su base de código.
@@ -151,7 +151,7 @@ Busque archivos usando patrones globales como `**/*.js` o `src/**/*.ts`. Devuelv
---
### lista
### list
Enumere archivos y directorios en una ruta determinada.
@@ -191,7 +191,7 @@ Para configurar qué servidores LSP están disponibles para su proyecto, consult
---
### parche
### patch
Aplicar parches a los archivos.
@@ -212,7 +212,7 @@ La herramienta `patch` está controlada por el permiso `edit`, que cubre todas l
---
### habilidad
### skill
Cargue una [habilidad](/docs/skills) (un archivo `SKILL.md`) y devuelva su contenido en la conversación.
@@ -227,7 +227,7 @@ Cargue una [habilidad](/docs/skills) (un archivo `SKILL.md`) y devuelva su conte
---
### todo escribir
### todowrite
Administre listas de tareas pendientes durante las sesiones de codificación.
@@ -248,7 +248,7 @@ Esta herramienta está deshabilitada para los subagentes de forma predeterminada
---
### todo leer
### todoread
Leer listas de tareas pendientes existentes.
@@ -269,7 +269,7 @@ Esta herramienta está deshabilitada para los subagentes de forma predeterminada
---
### búsqueda web
### webfetch
Obtener contenido web.
@@ -286,7 +286,7 @@ Permite que LLM busque y lea páginas web. Útil para buscar documentación o in
---
### búsqueda web
### websearch
Busque información en la web.
@@ -320,7 +320,7 @@ Utilice `websearch` cuando necesite encontrar información (descubrimiento) y `w
---
### pregunta
### question
Haga preguntas al usuario durante la ejecución.
@@ -354,7 +354,7 @@ Las herramientas personalizadas le permiten definir sus propias funciones a las
## MCP servidores
Los servidores MCP (Protocolo de contexto modelo) le permiten integrar herramientas y servicios externos. Esto incluye acceso a bases de datos, integraciones API y servicios de terceros.
Los servidores MCP (Model Context Protocol) le permiten integrar herramientas y servicios externos. Esto incluye acceso a bases de datos, integraciones API y servicios de terceros.
[Más información](/docs/mcp-servers) sobre la configuración de servidores MCP.
+1 -1
View File
@@ -181,7 +181,7 @@ Configurez les agents dans votre fichier de configuration `opencode.json` :
Vous pouvez également définir des agents à l'aide de fichiers markdown. Placez-les dans :
- Mondial : `~/.config/opencode/agents/`
- Global : `~/.config/opencode/agents/`
- Par projet : `.opencode/agents/`
```markdown title="~/.config/opencode/agents/review.md"
+4 -4
View File
@@ -179,7 +179,7 @@ Cela configure le flux de travail GitHub Actions nécessaire et vous guide tout
#### run
Exécutez l'agent GitHub. Ceci est généralement utilisé dans les actions GitHub.
Exécutez l'agent GitHub. Ceci est généralement utilisé dans les GitHub Actions.
```bash
opencode github run
@@ -536,7 +536,7 @@ opencode upgrade v0.1.48
---
## Drapeaux mondiaux
## Drapeaux globaux
La CLI opencode prend les indicateurs globaux suivants.
@@ -553,7 +553,7 @@ La CLI opencode prend les indicateurs globaux suivants.
OpenCode peut être configuré à l'aide de variables d'environnement.
| Variables | Tapez | Descriptif |
| Variables | Type | Descriptif |
| ------------------------------------- | ------- | --------------------------------------------------------------------------------- |
| `OPENCODE_AUTO_SHARE` | booléen | Partager automatiquement des sessions |
| `OPENCODE_GIT_BASH_PATH` | chaîne | Chemin vers l'exécutable Git Bash sur Windows |
@@ -586,7 +586,7 @@ OpenCode peut être configuré à l'aide de variables d'environnement.
Ces variables d'environnement activent des fonctionnalités expérimentales qui peuvent être modifiées ou supprimées.
| Variables | Tapez | Descriptif |
| Variables | Type | Descriptif |
| ----------------------------------------------- | ------- | --------------------------------------------------------------- |
| `OPENCODE_EXPERIMENTAL` | booléen | Activer toutes les fonctionnalités expérimentales |
| `OPENCODE_EXPERIMENTAL_ICON_DISCOVERY` | booléen | Activer la découverte d'icônes |
@@ -79,7 +79,7 @@ Vous pouvez maintenant exécuter cette commande dans TUI :
Vous pouvez également définir des commandes à l'aide de fichiers markdown. Placez-les dans :
- Mondial : `~/.config/opencode/commands/`
- Global : `~/.config/opencode/commands/`
- Par projet : `.opencode/commands/`
```markdown title="~/.config/opencode/commands/test.md"
+3 -3
View File
@@ -3,7 +3,7 @@ title: GitHub
description: Utilisez OpenCode dans les problèmes et les pull-requests GitHub.
---
OpenCode s'intègre à votre flux de travail GitHub. Mentionnez `/opencode` ou `/oc` dans votre commentaire, et OpenCode exécutera des tâches dans votre coureur d'actions GitHub.
OpenCode s'intègre à votre flux de travail GitHub. Mentionnez `/opencode` ou `/oc` dans votre commentaire, et OpenCode exécutera des tâches dans votre runner GitHub Actions.
---
@@ -11,7 +11,7 @@ OpenCode s'intègre à votre flux de travail GitHub. Mentionnez `/opencode` ou `
- **Problèmes de triage** : demandez à OpenCode d'examiner un problème et de vous l'expliquer.
- **Réparer et implémenter** : demandez à OpenCode de résoudre un problème ou d'implémenter une fonctionnalité. Et il fonctionnera dans une nouvelle branche et soumettra un PR avec tous les changements.
- **Sécurisé** : OpenCode court à l'intérieur des coureurs de votre GitHub.
- **Sécurisé** : OpenCode s'exécute à l'intérieur de vos runners GitHub.
---
@@ -87,7 +87,7 @@ Dans les **paramètres** de votre organisation ou de votre projet, développez *
- `prompt` : invite personnalisée facultative pour remplacer le comportement par défaut. Utilisez-le pour personnaliser la façon dont OpenCode traite les demandes.
- `token` : jeton d'accès GitHub facultatif pour effectuer des opérations telles que la création de commentaires, la validation de modifications et l'ouverture de demandes d'extraction. Par défaut, OpenCode utilise le jeton d'accès à l'installation de l'application OpenCode GitHub, de sorte que les validations, les commentaires et les demandes d'extraction apparaissent comme provenant de l'application.
Vous pouvez également utiliser le `GITHUB_TOKEN`](https://docs.github.com/en/actions/tutorials/authenticate-with-github_token) intégré du GitHub Action Runner sans installer l'application OpenCode GitHub. Assurez-vous simplement d'accorder les autorisations requises dans votre flux de travail :
Vous pouvez également utiliser le `GITHUB_TOKEN`](https://docs.github.com/en/actions/tutorials/authenticate-with-github_token) intégré du runner GitHub Actions sans installer l'application OpenCode GitHub. Assurez-vous simplement d'accorder les autorisations requises dans votre flux de travail :
```yaml
permissions:
+2 -2
View File
@@ -5,7 +5,7 @@ description: Utilisez OpenCode dans les problèmes GitLab et les demandes de fus
OpenCode s'intègre à votre flux de travail GitLab via votre pipeline CI/CD GitLab ou avec GitLab Duo.
Dans les deux cas, OpenCode fonctionnera sur vos coureurs GitLab.
Dans les deux cas, OpenCode fonctionnera sur vos runners GitLab.
---
@@ -56,7 +56,7 @@ Mentionnez `@opencode` dans un commentaire et OpenCode exécutera les tâches au
- **Problèmes de triage** : demandez à OpenCode d'examiner un problème et de vous l'expliquer.
- **Réparer et implémenter** : demandez à OpenCode de résoudre un problème ou d'implémenter une fonctionnalité.
Cela créera une nouvelle branche et déclenchera une demande de fusion avec les modifications.
- **Sécurisé** : OpenCode fonctionne sur vos coureurs GitLab.
- **Sécurisé** : OpenCode fonctionne sur vos runners GitLab.
---
+2 -2
View File
@@ -90,7 +90,7 @@ Vous pouvez également l'installer avec les commandes suivantes :
#### Windows
:::tip[Recommandé : utilisez WSL]
Pour une expérience optimale sur Windows, nous vous recommandons d'utiliser le sous-système [Windows pour Linux (WSL)](/docs/windows-wsl). Il offre de meilleures performances et une compatibilité totale avec les fonctionnalités de OpenCode.
Pour une expérience optimale sur Windows, nous vous recommandons d'utiliser le [Windows Subsystem for Linux (WSL)](/docs/windows-wsl). Il offre de meilleures performances et une compatibilité totale avec les fonctionnalités de OpenCode.
:::
- **Utiliser Chocolatey**
@@ -356,4 +356,4 @@ Voici un [exemple de conversation](https://opencode.ai/s/4XP1fce5) avec OpenCode
Et c'est tout ! Vous êtes désormais un pro de l'utilisation de OpenCode.
Pour vous l'approprier, nous vous recommandons de [choisir un thème](/docs/themes), [de personnaliser les raccourcis clavier](/docs/keybinds), de [configurer les formateurs de code](/docs/formatters), de [créer des commandes personnalisées](/docs/commands) ou de jouer avec la [OpenCode config](/docs/config).
Pour vous l'approprier, nous vous recommandons de [choisir un thème](/docs/themes), [de personnaliser les raccourcis clavier](/docs/keybinds), de [configurer les formateurs de code](/docs/formatters), de [créer des commandes personnalisées](/docs/commands) ou de jouer avec la [OpenCode Config](/docs/config).
+7 -8
View File
@@ -142,15 +142,14 @@ Se devi passare variabili d'ambiente (come `OPENCODE_API_KEY`), vedi [Configurin
## Support
OpenCode funziona via ACP come nel terminal. Tutte le funzionalita sono supportate:
OpenCode funziona via ACP come nel terminale. Tutte le funzionalita sono supportate:
:::note
Alcuni comandi slash integrati come `/undo` e `/redo` non sono attualmente supportati.
:::
- Built-in tools (file operations, terminal commands, etc.)
- Custom tools and slash commands
- MCP servers configured in your OpenCode config
- Project-specific rules from `AGENTS.md`
- Custom formatters and linters
- Agents and permissions system
- Strumenti integrati (operazioni sui file, comandi da terminale, ecc.)
- Strumenti personalizzati e comandi slash
- Server MCP configurati nella tua configurazione OpenCode
- Regole specifiche del progetto da `AGENTS.md`
- Formatter e linter personalizzati
- Sistema di agenti e permessi
+6 -6
View File
@@ -21,7 +21,7 @@ opencode run "Explain how closures work in JavaScript"
### tui
Avvia l'interfaccia testuale di OpenCode (terminal user interface).
Avvia l'interfaccia testuale di OpenCode (interfaccia utente da terminale).
```bash
opencode [project]
@@ -60,7 +60,7 @@ opencode agent [command]
### attach
Collega un terminal a un backend server di OpenCode gia' in esecuzione avviato tramite i comandi `serve` o `web`.
Collega un terminale a un backend server di OpenCode gia' in esecuzione avviato tramite i comandi `serve` o `web`.
```bash
opencode attach [url]
@@ -72,7 +72,7 @@ Questo consente di usare la TUI con un backend OpenCode remoto. Per esempio:
# Start the backend server for web/mobile access
opencode web --port 4096 --hostname 0.0.0.0
# In another terminal, attach the TUI to the running backend
# In un altro terminale, collega la TUI al backend in esecuzione
opencode attach http://10.20.30.40:4096
```
@@ -326,10 +326,10 @@ opencode run Explain the use of context in Go
Puoi anche collegarti a una istanza `opencode serve` gia' in esecuzione per evitare i cold boot dei server MCP ad ogni esecuzione:
```bash
# Start a headless server in one terminal
# Avvia un server headless in un terminale
opencode serve
# In another terminal, run commands that attach to it
# In un altro terminale, esegui comandi che si collegano ad esso
opencode run --attach http://localhost:4096 "Explain async/await in JavaScript"
```
@@ -562,7 +562,7 @@ OpenCode puo' essere configurato tramite variabili d'ambiente.
| `OPENCODE_CONFIG_CONTENT` | string | Contenuto JSON di config inline |
| `OPENCODE_DISABLE_AUTOUPDATE` | boolean | Disabilita i controlli automatici di aggiornamento |
| `OPENCODE_DISABLE_PRUNE` | boolean | Disabilita la potatura dei dati vecchi |
| `OPENCODE_DISABLE_TERMINAL_TITLE` | boolean | Disabilita aggiornamenti automatici del titolo terminal |
| `OPENCODE_DISABLE_TERMINAL_TITLE` | boolean | Disabilita aggiornamenti automatici del titolo terminale |
| `OPENCODE_PERMISSION` | string | Config permessi JSON inline |
| `OPENCODE_DISABLE_DEFAULT_PLUGINS` | boolean | Disabilita i plugin di default |
| `OPENCODE_DISABLE_LSP_DOWNLOAD` | boolean | Disabilita download automatico dei server LSP |
+1 -1
View File
@@ -169,7 +169,7 @@ Opzioni disponibili:
- `scroll_acceleration.enabled` - Abilita l'accelerazione di scorrimento in stile macOS. **Ha precedenza su `scroll_speed`.**
- `scroll_speed` - Moltiplicatore personalizzato della velocita di scorrimento (predefinito: `3`, minimo: `1`). Ignorato se `scroll_acceleration.enabled` e `true`.
- `diff_style` - Controlla la resa delle diff. `"auto"` si adatta alla larghezza del terminal, `"stacked"` mostra sempre una singola colonna.
- `diff_style` - Controlla la resa delle diff. `"auto"` si adatta alla larghezza del terminale, `"stacked"` mostra sempre una singola colonna.
[Scopri di piu sull'uso della TUI](/docs/tui).
@@ -58,7 +58,7 @@ Puoi anche dare un'occhiata a [awesome-opencode](https://github.com/awesome-open
| [opencode.nvim](https://github.com/NickvanDyke/opencode.nvim) | Plugin Neovim per prompt consapevoli dell'editor, costruito sull'API |
| [portal](https://github.com/hosenur/portal) | Web UI mobile-first per OpenCode su Tailscale/VPN |
| [opencode plugin template](https://github.com/zenobi-us/opencode-plugin-template/) | Template per costruire plugin OpenCode |
| [opencode.nvim](https://github.com/sudo-tee/opencode.nvim) | Frontend Neovim per opencode: un agente di coding AI per terminal |
| [opencode.nvim](https://github.com/sudo-tee/opencode.nvim) | Frontend Neovim per opencode: un agente di coding AI per terminale |
| [ai-sdk-provider-opencode-sdk](https://github.com/ben-vargas/ai-sdk-provider-opencode-sdk) | Provider Vercel AI SDK per usare OpenCode tramite @opencode-ai/sdk |
| [OpenChamber](https://github.com/btriapitsyn/openchamber) | App Web/Desktop ed estensione VS Code per OpenCode |
| [OpenCode-Obsidian](https://github.com/mtymek/opencode-obsidian) | Plugin Obsidian che integra OpenCode nella UI di Obsidian |

Some files were not shown because too many files have changed in this diff Show More