Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8056842baf | ||
|
|
8ea1df1560 | ||
|
|
52ae62d4cc | ||
|
|
3c11eda350 | ||
|
|
c6ec2f47ef | ||
|
|
0fd6f365be | ||
|
|
60bdb6e9ba | ||
|
|
6e9cd576ea | ||
|
|
53ec15a56a | ||
|
|
a90b62267f | ||
|
|
24556331c8 | ||
|
|
39145b99e8 | ||
|
|
0afa6e03a8 | ||
|
|
7a3c775dc1 | ||
|
|
3ea58bb790 | ||
|
|
50c705cd2d | ||
|
|
3894c217cc | ||
|
|
66c2bb8f37 | ||
|
|
1bbbd51d48 | ||
|
|
50f3e74d05 | ||
|
|
21475a1dfd | ||
|
|
dce4c05fa9 | ||
|
|
8c56571ef9 | ||
|
|
92a77b72fb |
@@ -15,4 +15,5 @@ kitlangton
|
||||
kommander
|
||||
r44vc0rp
|
||||
rekram1-node
|
||||
-spider-yamet clawdbot/llm psychosis, spam pinging the team
|
||||
thdxr
|
||||
|
||||
@@ -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
@@ -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,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>
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.1.55",
|
||||
"version": "1.1.56",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
@@ -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,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",
|
||||
|
||||
@@ -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,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,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"private": true,
|
||||
"version": "1.1.55",
|
||||
"version": "1.1.56",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.1.55",
|
||||
"version": "1.1.56",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -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,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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,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 {
|
||||
|
||||
@@ -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)
|
||||
},
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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++
|
||||
|
||||
@@ -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(
|
||||
() => {
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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)
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
@@ -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,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,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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.1.55",
|
||||
"version": "1.1.56",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.1.55",
|
||||
"version": "1.1.56",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/util",
|
||||
"version": "1.1.55",
|
||||
"version": "1.1.56",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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.
|
||||

|
||||

|
||||
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 |
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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 |
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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` fø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).
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
@@ -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.
|
||||
|
||||

|
||||

|
||||
|
||||
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).
|
||||
|
||||
@@ -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).
|
||||
Ophæv 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
|
||||
|
||||
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 |
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user