Просмотр исходного кода

merge origin/dev into fix/stale-running-session-ui

Shoubhit Dash 1 месяц назад
Родитель
Сommit
c19cbccf06
34 измененных файлов с 270 добавлено и 146 удалено
  1. 9 1
      .github/actions/setup-bun/action.yml
  2. 16 16
      bun.lock
  3. 22 0
      packages/app/e2e/session/session-model-persistence.spec.ts
  4. 1 1
      packages/app/package.json
  5. 1 2
      packages/app/src/components/session/session-header.tsx
  6. 3 2
      packages/app/src/context/local.tsx
  7. 1 1
      packages/console/app/package.json
  8. 1 1
      packages/console/core/package.json
  9. 1 1
      packages/console/function/package.json
  10. 1 1
      packages/console/mail/package.json
  11. 1 1
      packages/desktop-electron/package.json
  12. 1 1
      packages/desktop/package.json
  13. 1 1
      packages/enterprise/package.json
  14. 6 6
      packages/extensions/zed/extension.toml
  15. 1 1
      packages/function/package.json
  16. 1 1
      packages/opencode/package.json
  17. 7 4
      packages/opencode/src/cli/cmd/account.ts
  18. 1 1
      packages/opencode/src/project/vcs.ts
  19. 2 1
      packages/opencode/src/provider/error.ts
  20. 6 2
      packages/opencode/src/session/llm.ts
  21. 1 0
      packages/opencode/src/session/prompt.ts
  22. 91 1
      packages/opencode/test/session/llm.test.ts
  23. 20 0
      packages/opencode/test/session/message-v2.test.ts
  24. 1 1
      packages/plugin/package.json
  25. 1 1
      packages/sdk/js/package.json
  26. 1 1
      packages/slack/package.json
  27. 1 1
      packages/ui/package.json
  28. 2 12
      packages/ui/src/components/message-part.css
  29. 1 1
      packages/util/package.json
  30. 1 1
      packages/web/package.json
  31. 1 0
      packages/web/src/content/docs/ecosystem.mdx
  32. 63 77
      packages/web/src/content/docs/es/index.mdx
  33. 2 4
      packages/web/src/content/docs/zen.mdx
  34. 1 1
      sdks/vscode/package.json

+ 9 - 1
.github/actions/setup-bun/action.yml

@@ -41,5 +41,13 @@ runs:
       shell: bash
 
     - name: Install dependencies
-      run: bun install
+      run: |
+        # Workaround for patched peer variants
+        # e.g. ./patches/ for standard-openapi
+        # https://github.com/oven-sh/bun/issues/28147
+        if [ "$RUNNER_OS" = "Windows" ]; then
+          bun install --linker hoisted
+        else
+          bun install
+        fi
       shell: bash

+ 16 - 16
bun.lock

@@ -26,7 +26,7 @@
     },
     "packages/app": {
       "name": "@opencode-ai/app",
-      "version": "1.2.26",
+      "version": "1.2.27",
       "dependencies": {
         "@kobalte/core": "catalog:",
         "@opencode-ai/sdk": "workspace:*",
@@ -77,7 +77,7 @@
     },
     "packages/console/app": {
       "name": "@opencode-ai/console-app",
-      "version": "1.2.26",
+      "version": "1.2.27",
       "dependencies": {
         "@cloudflare/vite-plugin": "1.15.2",
         "@ibm/plex": "6.4.1",
@@ -111,7 +111,7 @@
     },
     "packages/console/core": {
       "name": "@opencode-ai/console-core",
-      "version": "1.2.26",
+      "version": "1.2.27",
       "dependencies": {
         "@aws-sdk/client-sts": "3.782.0",
         "@jsx-email/render": "1.1.1",
@@ -138,7 +138,7 @@
     },
     "packages/console/function": {
       "name": "@opencode-ai/console-function",
-      "version": "1.2.26",
+      "version": "1.2.27",
       "dependencies": {
         "@ai-sdk/anthropic": "2.0.0",
         "@ai-sdk/openai": "2.0.2",
@@ -162,7 +162,7 @@
     },
     "packages/console/mail": {
       "name": "@opencode-ai/console-mail",
-      "version": "1.2.26",
+      "version": "1.2.27",
       "dependencies": {
         "@jsx-email/all": "2.2.3",
         "@jsx-email/cli": "1.4.3",
@@ -186,7 +186,7 @@
     },
     "packages/desktop": {
       "name": "@opencode-ai/desktop",
-      "version": "1.2.26",
+      "version": "1.2.27",
       "dependencies": {
         "@opencode-ai/app": "workspace:*",
         "@opencode-ai/ui": "workspace:*",
@@ -219,7 +219,7 @@
     },
     "packages/desktop-electron": {
       "name": "@opencode-ai/desktop-electron",
-      "version": "1.2.26",
+      "version": "1.2.27",
       "dependencies": {
         "@opencode-ai/app": "workspace:*",
         "@opencode-ai/ui": "workspace:*",
@@ -250,7 +250,7 @@
     },
     "packages/enterprise": {
       "name": "@opencode-ai/enterprise",
-      "version": "1.2.26",
+      "version": "1.2.27",
       "dependencies": {
         "@opencode-ai/ui": "workspace:*",
         "@opencode-ai/util": "workspace:*",
@@ -279,7 +279,7 @@
     },
     "packages/function": {
       "name": "@opencode-ai/function",
-      "version": "1.2.26",
+      "version": "1.2.27",
       "dependencies": {
         "@octokit/auth-app": "8.0.1",
         "@octokit/rest": "catalog:",
@@ -295,7 +295,7 @@
     },
     "packages/opencode": {
       "name": "opencode",
-      "version": "1.2.26",
+      "version": "1.2.27",
       "bin": {
         "opencode": "./bin/opencode",
       },
@@ -416,7 +416,7 @@
     },
     "packages/plugin": {
       "name": "@opencode-ai/plugin",
-      "version": "1.2.26",
+      "version": "1.2.27",
       "dependencies": {
         "@opencode-ai/sdk": "workspace:*",
         "zod": "catalog:",
@@ -440,7 +440,7 @@
     },
     "packages/sdk/js": {
       "name": "@opencode-ai/sdk",
-      "version": "1.2.26",
+      "version": "1.2.27",
       "devDependencies": {
         "@hey-api/openapi-ts": "0.90.10",
         "@tsconfig/node22": "catalog:",
@@ -451,7 +451,7 @@
     },
     "packages/slack": {
       "name": "@opencode-ai/slack",
-      "version": "1.2.26",
+      "version": "1.2.27",
       "dependencies": {
         "@opencode-ai/sdk": "workspace:*",
         "@slack/bolt": "^3.17.1",
@@ -486,7 +486,7 @@
     },
     "packages/ui": {
       "name": "@opencode-ai/ui",
-      "version": "1.2.26",
+      "version": "1.2.27",
       "dependencies": {
         "@kobalte/core": "catalog:",
         "@opencode-ai/sdk": "workspace:*",
@@ -532,7 +532,7 @@
     },
     "packages/util": {
       "name": "@opencode-ai/util",
-      "version": "1.2.26",
+      "version": "1.2.27",
       "dependencies": {
         "zod": "catalog:",
       },
@@ -543,7 +543,7 @@
     },
     "packages/web": {
       "name": "@opencode-ai/web",
-      "version": "1.2.26",
+      "version": "1.2.27",
       "dependencies": {
         "@astrojs/cloudflare": "12.6.3",
         "@astrojs/markdown-remark": "6.3.1",

+ 22 - 0
packages/app/e2e/session/session-model-persistence.spec.ts

@@ -349,3 +349,25 @@ test("session model restore across workspaces", async ({ page, withProject }) =>
     await waitFooter(page, firstState)
   })
 })
+
+test("variant preserved when switching agent modes", async ({ page, withProject }) => {
+  await page.setViewportSize({ width: 1440, height: 900 })
+
+  await withProject(async ({ directory, gotoSession }) => {
+    await gotoSession()
+
+    await ensureVariant(page, directory)
+    const updated = await chooseDifferentVariant(page)
+
+    const available = await agents(page)
+    const other = available.find((name) => name !== updated.agent)
+    test.skip(!other, "only one agent available")
+    if (!other) return
+
+    await choose(page, promptAgentSelector, other)
+    await waitFooter(page, { agent: other, variant: updated.variant })
+
+    await choose(page, promptAgentSelector, updated.agent)
+    await waitFooter(page, { agent: updated.agent, variant: updated.variant })
+  })
+})

+ 1 - 1
packages/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/app",
-  "version": "1.2.26",
+  "version": "1.2.27",
   "description": "",
   "type": "module",
   "exports": {

+ 1 - 2
packages/app/src/components/session/session-header.tsx

@@ -326,7 +326,7 @@ export function SessionHeader() {
                       <div class="flex h-[24px] box-border items-center rounded-md border border-border-weak-base bg-surface-panel overflow-hidden">
                         <Button
                           variant="ghost"
-                          class="rounded-none h-full py-0 pr-1.5 pl-px gap-1.5 border-none shadow-none disabled:!cursor-default"
+                          class="rounded-none h-full px-0.5 border-none shadow-none disabled:!cursor-default"
                           classList={{
                             "bg-surface-raised-base-active": opening(),
                           }}
@@ -339,7 +339,6 @@ export function SessionHeader() {
                               <Spinner class="size-3.5" style={{ color: tint() ?? "var(--icon-base)" }} />
                             </Show>
                           </div>
-                          <span class="text-12-regular text-text-strong">{language.t("common.open")}</span>
                         </Button>
                         <DropdownMenu
                           gutter={4}

+ 3 - 2
packages/app/src/context/local.tsx

@@ -192,10 +192,11 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
             model: item.model,
             variant: item.variant ?? null,
           })
+          const prev = scope()
           const next = {
             agent: item.name,
-            model: item.model,
-            variant: item.variant,
+            model: item.model ?? prev?.model,
+            variant: item.variant ?? prev?.variant,
           } satisfies State
           const session = id()
           if (session) {

+ 1 - 1
packages/console/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/console-app",
-  "version": "1.2.26",
+  "version": "1.2.27",
   "type": "module",
   "license": "MIT",
   "scripts": {

+ 1 - 1
packages/console/core/package.json

@@ -1,7 +1,7 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
   "name": "@opencode-ai/console-core",
-  "version": "1.2.26",
+  "version": "1.2.27",
   "private": true,
   "type": "module",
   "license": "MIT",

+ 1 - 1
packages/console/function/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/console-function",
-  "version": "1.2.26",
+  "version": "1.2.27",
   "$schema": "https://json.schemastore.org/package.json",
   "private": true,
   "type": "module",

+ 1 - 1
packages/console/mail/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/console-mail",
-  "version": "1.2.26",
+  "version": "1.2.27",
   "dependencies": {
     "@jsx-email/all": "2.2.3",
     "@jsx-email/cli": "1.4.3",

+ 1 - 1
packages/desktop-electron/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@opencode-ai/desktop-electron",
   "private": true,
-  "version": "1.2.26",
+  "version": "1.2.27",
   "type": "module",
   "license": "MIT",
   "homepage": "https://opencode.ai",

+ 1 - 1
packages/desktop/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@opencode-ai/desktop",
   "private": true,
-  "version": "1.2.26",
+  "version": "1.2.27",
   "type": "module",
   "license": "MIT",
   "scripts": {

+ 1 - 1
packages/enterprise/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/enterprise",
-  "version": "1.2.26",
+  "version": "1.2.27",
   "private": true,
   "type": "module",
   "license": "MIT",

+ 6 - 6
packages/extensions/zed/extension.toml

@@ -1,7 +1,7 @@
 id = "opencode"
 name = "OpenCode"
 description = "The open source coding agent."
-version = "1.2.26"
+version = "1.2.27"
 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.2.26/opencode-darwin-arm64.zip"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.27/opencode-darwin-arm64.zip"
 cmd = "./opencode"
 args = ["acp"]
 
 [agent_servers.opencode.targets.darwin-x86_64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.26/opencode-darwin-x64.zip"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.27/opencode-darwin-x64.zip"
 cmd = "./opencode"
 args = ["acp"]
 
 [agent_servers.opencode.targets.linux-aarch64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.26/opencode-linux-arm64.tar.gz"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.27/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.2.26/opencode-linux-x64.tar.gz"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.27/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.2.26/opencode-windows-x64.zip"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.2.27/opencode-windows-x64.zip"
 cmd = "./opencode.exe"
 args = ["acp"]

+ 1 - 1
packages/function/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/function",
-  "version": "1.2.26",
+  "version": "1.2.27",
   "$schema": "https://json.schemastore.org/package.json",
   "private": true,
   "type": "module",

+ 1 - 1
packages/opencode/package.json

@@ -1,6 +1,6 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
-  "version": "1.2.26",
+  "version": "1.2.27",
   "name": "opencode",
   "type": "module",
   "license": "MIT",

+ 7 - 4
packages/opencode/src/cli/cmd/account.ts

@@ -11,6 +11,11 @@ const openBrowser = (url: string) => Effect.promise(() => open(url).catch(() =>
 
 const println = (msg: string) => Effect.sync(() => UI.println(msg))
 
+const isActiveOrgChoice = (
+  active: Option.Option<{ id: AccountID; active_org_id: OrgID | null }>,
+  choice: { accountID: AccountID; orgID: OrgID },
+) => Option.isSome(active) && active.value.id === choice.accountID && active.value.active_org_id === choice.orgID
+
 const loginEffect = Effect.fn("login")(function* (url: string) {
   const service = yield* AccountService
 
@@ -99,11 +104,10 @@ const switchEffect = Effect.fn("switch")(function* () {
   if (groups.length === 0) return yield* println("Not logged in")
 
   const active = yield* service.active()
-  const activeOrgID = Option.flatMap(active, (a) => Option.fromNullishOr(a.active_org_id))
 
   const opts = groups.flatMap((group) =>
     group.orgs.map((org) => {
-      const isActive = Option.isSome(activeOrgID) && activeOrgID.value === org.id
+      const isActive = isActiveOrgChoice(active, { accountID: group.account.id, orgID: org.id })
       return {
         value: { orgID: org.id, accountID: group.account.id, label: org.name },
         label: isActive
@@ -132,11 +136,10 @@ const orgsEffect = Effect.fn("orgs")(function* () {
   if (!groups.some((group) => group.orgs.length > 0)) return yield* println("No orgs found")
 
   const active = yield* service.active()
-  const activeOrgID = Option.flatMap(active, (a) => Option.fromNullishOr(a.active_org_id))
 
   for (const group of groups) {
     for (const org of group.orgs) {
-      const isActive = Option.isSome(activeOrgID) && activeOrgID.value === org.id
+      const isActive = isActiveOrgChoice(active, { accountID: group.account.id, orgID: org.id })
       const dot = isActive ? UI.Style.TEXT_SUCCESS + "●" + UI.Style.TEXT_NORMAL : " "
       const name = isActive ? UI.Style.TEXT_HIGHLIGHT_BOLD + org.name + UI.Style.TEXT_NORMAL : org.name
       const email = UI.Style.TEXT_DIM + group.account.email + UI.Style.TEXT_NORMAL

+ 1 - 1
packages/opencode/src/project/vcs.ts

@@ -47,7 +47,7 @@ export namespace Vcs {
       log.info("initialized", { branch: current })
 
       const unsubscribe = Bus.subscribe(FileWatcher.Event.Updated, async (evt) => {
-        if (evt.properties.file.endsWith("HEAD")) return
+        if (!evt.properties.file.endsWith("HEAD")) return
         const next = await currentBranch()
         if (next !== current) {
           log.info("branch changed", { from: current, to: next })

+ 2 - 1
packages/opencode/src/provider/error.ts

@@ -167,7 +167,8 @@ export namespace ProviderError {
 
   export function parseAPICallError(input: { providerID: ProviderID; error: APICallError }): ParsedAPICallError {
     const m = message(input.providerID, input.error)
-    if (isOverflow(m) || input.error.statusCode === 413) {
+    const body = json(input.error.responseBody)
+    if (isOverflow(m) || input.error.statusCode === 413 || body?.error?.code === "context_length_exceeded") {
       return {
         type: "context_overflow",
         message: m,

+ 6 - 2
packages/opencode/src/session/llm.ts

@@ -32,6 +32,7 @@ export namespace LLM {
     sessionID: string
     model: Provider.Model
     agent: Agent.Info
+    permission?: PermissionNext.Ruleset
     system: string[]
     abort: AbortSignal
     messages: ModelMessage[]
@@ -255,8 +256,11 @@ export namespace LLM {
     })
   }
 
-  async function resolveTools(input: Pick<StreamInput, "tools" | "agent" | "user">) {
-    const disabled = PermissionNext.disabled(Object.keys(input.tools), input.agent.permission)
+  async function resolveTools(input: Pick<StreamInput, "tools" | "agent" | "permission" | "user">) {
+    const disabled = PermissionNext.disabled(
+      Object.keys(input.tools),
+      PermissionNext.merge(input.agent.permission, input.permission ?? []),
+    )
     for (const tool of Object.keys(input.tools)) {
       if (input.user.tools?.[tool] === false || disabled.has(tool)) {
         delete input.tools[tool]

+ 1 - 0
packages/opencode/src/session/prompt.ts

@@ -664,6 +664,7 @@ export namespace SessionPrompt {
         return processor.process({
           user: lastUser,
           agent,
+          permission: session.permission,
           abort,
           sessionID,
           system,

+ 91 - 1
packages/opencode/test/session/llm.test.ts

@@ -1,6 +1,7 @@
 import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test"
 import path from "path"
-import type { ModelMessage } from "ai"
+import { tool, type ModelMessage } from "ai"
+import z from "zod"
 import { LLM } from "../../src/session/llm"
 import { Global } from "../../src/global"
 import { Instance } from "../../src/project/instance"
@@ -325,6 +326,95 @@ describe("session.llm.stream", () => {
     })
   })
 
+  test("keeps tools enabled by prompt permissions", async () => {
+    const server = state.server
+    if (!server) {
+      throw new Error("Server not initialized")
+    }
+
+    const providerID = "alibaba"
+    const modelID = "qwen-plus"
+    const fixture = await loadFixture(providerID, modelID)
+    const model = fixture.model
+
+    const request = waitRequest(
+      "/chat/completions",
+      new Response(createChatStream("Hello"), {
+        status: 200,
+        headers: { "Content-Type": "text/event-stream" },
+      }),
+    )
+
+    await using tmp = await tmpdir({
+      init: async (dir) => {
+        await Bun.write(
+          path.join(dir, "opencode.json"),
+          JSON.stringify({
+            $schema: "https://opencode.ai/config.json",
+            enabled_providers: [providerID],
+            provider: {
+              [providerID]: {
+                options: {
+                  apiKey: "test-key",
+                  baseURL: `${server.url.origin}/v1`,
+                },
+              },
+            },
+          }),
+        )
+      },
+    })
+
+    await Instance.provide({
+      directory: tmp.path,
+      fn: async () => {
+        const resolved = await Provider.getModel(ProviderID.make(providerID), ModelID.make(model.id))
+        const sessionID = SessionID.make("session-test-tools")
+        const agent = {
+          name: "test",
+          mode: "primary",
+          options: {},
+          permission: [{ permission: "question", pattern: "*", action: "deny" }],
+        } satisfies Agent.Info
+
+        const user = {
+          id: MessageID.make("user-tools"),
+          sessionID,
+          role: "user",
+          time: { created: Date.now() },
+          agent: agent.name,
+          model: { providerID: ProviderID.make(providerID), modelID: resolved.id },
+          tools: { question: true },
+        } satisfies MessageV2.User
+
+        const stream = await LLM.stream({
+          user,
+          sessionID,
+          model: resolved,
+          agent,
+          permission: [{ permission: "question", pattern: "*", action: "allow" }],
+          system: ["You are a helpful assistant."],
+          abort: new AbortController().signal,
+          messages: [{ role: "user", content: "Hello" }],
+          tools: {
+            question: tool({
+              description: "Ask a question",
+              inputSchema: z.object({}),
+              execute: async () => ({ output: "" }),
+            }),
+          },
+        })
+
+        for await (const _ of stream.fullStream) {
+        }
+
+        const capture = await request
+        const tools = capture.body.tools as Array<{ function?: { name?: string } }> | undefined
+        expect(tools?.some((item) => item.function?.name === "question")).toBe(true)
+      },
+    })
+  })
+
   test("sends responses API payload for OpenAI models", async () => {
     const server = state.server
     if (!server) {

+ 20 - 0
packages/opencode/test/session/message-v2.test.ts

@@ -869,6 +869,26 @@ describe("session.message-v2.fromError", () => {
     })
   })
 
+  test("detects context overflow from context_length_exceeded code in response body", () => {
+    const error = new APICallError({
+      message: "Request failed",
+      url: "https://example.com",
+      requestBodyValues: {},
+      statusCode: 422,
+      responseHeaders: { "content-type": "application/json" },
+      responseBody: JSON.stringify({
+        error: {
+          message: "Some message",
+          type: "invalid_request_error",
+          code: "context_length_exceeded",
+        },
+      }),
+      isRetryable: false,
+    })
+    const result = MessageV2.fromError(error, { providerID })
+    expect(MessageV2.ContextOverflowError.isInstance(result)).toBe(true)
+  })
+
   test("does not classify 429 no body as context overflow", () => {
     const result = MessageV2.fromError(
       new APICallError({

+ 1 - 1
packages/plugin/package.json

@@ -1,7 +1,7 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
   "name": "@opencode-ai/plugin",
-  "version": "1.2.26",
+  "version": "1.2.27",
   "type": "module",
   "license": "MIT",
   "scripts": {

+ 1 - 1
packages/sdk/js/package.json

@@ -1,7 +1,7 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
   "name": "@opencode-ai/sdk",
-  "version": "1.2.26",
+  "version": "1.2.27",
   "type": "module",
   "license": "MIT",
   "scripts": {

+ 1 - 1
packages/slack/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/slack",
-  "version": "1.2.26",
+  "version": "1.2.27",
   "type": "module",
   "license": "MIT",
   "scripts": {

+ 1 - 1
packages/ui/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/ui",
-  "version": "1.2.26",
+  "version": "1.2.27",
   "type": "module",
   "license": "MIT",
   "exports": {

+ 2 - 12
packages/ui/src/components/message-part.css

@@ -1050,18 +1050,8 @@
     line-height: var(--line-height-large);
     color: var(--text-base);
     min-width: 0;
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-  }
-
-  [data-slot="question-option"][data-custom="true"] {
-    [data-slot="option-description"] {
-      overflow: visible;
-      text-overflow: clip;
-      white-space: normal;
-      overflow-wrap: anywhere;
-    }
+    overflow-wrap: anywhere;
+    white-space: normal;
   }
 
   [data-slot="question-custom"] {

+ 1 - 1
packages/util/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/util",
-  "version": "1.2.26",
+  "version": "1.2.27",
   "private": true,
   "type": "module",
   "license": "MIT",

+ 1 - 1
packages/web/package.json

@@ -2,7 +2,7 @@
   "name": "@opencode-ai/web",
   "type": "module",
   "license": "MIT",
-  "version": "1.2.26",
+  "version": "1.2.27",
   "scripts": {
     "dev": "astro dev",
     "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",

+ 1 - 0
packages/web/src/content/docs/ecosystem.mdx

@@ -49,6 +49,7 @@ You can also check out [awesome-opencode](https://github.com/awesome-opencode/aw
 | [opencode-workspace](https://github.com/kdcokenny/opencode-workspace)                              | Bundled multi-agent orchestration harness – 16 components, one install                            |
 | [opencode-worktree](https://github.com/kdcokenny/opencode-worktree)                                | Zero-friction git worktrees for OpenCode                                                          |
 | [opencode-sentry-monitor](https://github.com/stolinski/opencode-sentry-monitor)                    | Trace and debug your AI agents with Sentry AI Monitoring                                          |
+| [opencode-firecrawl](https://github.com/firecrawl/opencode-firecrawl)                              | Web scraping, crawling, and search via the Firecrawl CLI                                          |
 
 ---
 

+ 63 - 77
packages/web/src/content/docs/es/index.mdx

@@ -1,23 +1,23 @@
 ---
 title: Introducción
-description: Comience con OpenCode.
+description: Comience a usar OpenCode.
 ---
 
 import { Tabs, TabItem } from "@astrojs/starlight/components"
 import config from "../../../../config.mjs"
 export const console = config.console
 
-[**OpenCode**](/) es un agente de codificación de IA de código abierto. Está disponible como interfaz basada en terminal, aplicación de escritorio o extensión IDE.
+[**OpenCode**](/) es un agente de codigo de IA de código abierto. Está disponible como interfaz basada en terminal, aplicación de escritorio o extensión IDE.
 
 ![OpenCode TUI con el tema opencode](../../../assets/lander/screenshot.png)
 
-Empecemos.
+Comencemos.
 
 ---
 
 #### Requisitos previos
 
-Para usar OpenCode en su terminal, necesitará:
+Para usar OpenCode en la terminal, necesitará:
 
 1. Un emulador de terminal moderno como:
    - [WezTerm](https://wezterm.org), multiplataforma
@@ -25,7 +25,7 @@ Para usar OpenCode en su terminal, necesitará:
    - [Ghostty](https://ghostty.org), Linux y macOS
    - [Kitty](https://sw.kovidgoyal.net/kitty/), Linux y macOS
 
-2. Claves API para los LLM proveedores que desea utilizar.
+2. Claves de API de los proveedores de LLM que quiera usar.
 
 ---
 
@@ -37,7 +37,7 @@ La forma más sencilla de instalar OpenCode es mediante el script de instalació
 curl -fsSL https://opencode.ai/install | bash
 ```
 
-También puedes instalarlo con los siguientes comandos:
+También puedes instalarlo con alguno de los siguientes métodos:
 
 - **Usando Node.js**
 
@@ -91,7 +91,7 @@ También puedes instalarlo con los siguientes comandos:
 #### Windows
 
 :::tip[Recomendado: Usar WSL]
-Para obtener la mejor experiencia en Windows, recomendamos utilizar [Windows Subsystem for Linux (WSL)](/docs/windows-wsl). Proporciona un mejor rendimiento y compatibilidad total con las funciones de OpenCode.
+Para obtener la mejor experiencia en Windows, recomendamos utilizar [Windows Subsystem for Linux (WSL)](/docs/windows-wsl). Ofrece mejor rendimiento y compatibilidad total con las funciones de OpenCode.
 :::
 
 - **Usando Chocolatey**
@@ -124,28 +124,28 @@ Para obtener la mejor experiencia en Windows, recomendamos utilizar [Windows Sub
   docker run -it --rm ghcr.io/anomalyco/opencode
   ```
 
-Actualmente se encuentra en progreso el soporte para instalar OpenCode en Windows usando Bun.
+El soporte para instalar OpenCode en Windows usando Bun todavía está en desarrollo.
 
-También puede obtener el binario de [Versiones](https://github.com/anomalyco/opencode/releases).
+También puede obtener el binario desde [Versiones](https://github.com/anomalyco/opencode/releases).
 
 ---
 
 ## Configuración
 
-Con OpenCode puedes usar cualquier proveedor LLM configurando sus claves API.
+Con OpenCode, puede usar cualquier proveedor de LLM configurando sus claves de API.
 
-Si es nuevo en el uso de proveedores LLM, le recomendamos usar [OpenCode Zen](/docs/zen).
-Es una lista seleccionada de modelos que han sido probados y verificados por el equipo de OpenCode.
+Si es nuevo en el uso de proveedores de LLM, le recomendamos usar [OpenCode Zen](/docs/zen).
+Es una selección de modelos probados y verificados por el equipo de OpenCode.
 
-1. Ejecute el comando `/connect` en TUI, seleccione opencode y diríjase a [opencode.ai/auth](https://opencode.ai/auth).
+1. Ejecute el comando `/connect` en la TUI, seleccione opencode y diríjase a [opencode.ai/auth](https://opencode.ai/auth).
 
    ```txt
    /connect
    ```
 
-2. Inicie sesión, agregue sus datos de facturación y copie su clave API.
+2. Inicie sesión, agregue sus datos de facturación y copie su clave de API.
 
-3. Pega tu clave API.
+3. Pega tu clave de API.
 
    ```txt
    ┌ API key
@@ -154,50 +154,45 @@ Es una lista seleccionada de modelos que han sido probados y verificados por el
    └ enter
    ```
 
-Alternativamente, puede seleccionar uno de los otros proveedores. [Más información](/docs/providers#directory).
+También puede seleccionar otro proveedor. [Más información](/docs/providers#directory).
 
 ---
 
 ## Inicializar
 
-Ahora que ha configurado un proveedor, puede navegar a un proyecto que
-quieres trabajar.
+Ahora que ya configuró un proveedor, vaya al proyecto en el que quiera trabajar.
 
 ```bash
 cd /path/to/project
 ```
 
-Y ejecute OpenCode.
+Luego, ejecute OpenCode.
 
 ```bash
 opencode
 ```
 
-A continuación, inicialice OpenCode para el proyecto ejecutando el siguiente comando.
+A continuación, inicialice OpenCode para el proyecto con el siguiente comando:
 
 ```bash frame="none"
 /init
 ```
 
-Esto hará que OpenCode analice su proyecto y cree un archivo `AGENTS.md` en
-la raíz del proyecto.
+OpenCode analizará su proyecto y creará un archivo AGENTS.md en la raíz.
 
 :::tip
-Debes enviar el archivo `AGENTS.md` de tu proyecto a Git.
+Asegúrese de versionar en Git el archivo AGENTS.md de su proyecto.
 :::
 
-Esto ayuda a OpenCode a comprender la estructura del proyecto y los patrones de codificación.
-usado.
+Esto ayuda a OpenCode a comprender la estructura del proyecto y los patrones de código que se usan en él.
 
 ---
 
 ## Usar
 
-Ahora está listo para usar OpenCode para trabajar en su proyecto. No dudes en preguntarle
-¡cualquier cosa!
+Ahora ya está listo para usar OpenCode en su proyecto. Puede pedirle desde explicaciones del código hasta cambios concretos.
 
-Si es nuevo en el uso de un agente de codificación de IA, aquí hay algunos ejemplos que podrían
-ayuda.
+Si es la primera vez que usa un agente de codigo con IA, estos ejemplos pueden servirle como punto de partida.
 
 ---
 
@@ -206,126 +201,117 @@ ayuda.
 Puede pedirle a OpenCode que le explique el código base.
 
 :::tip
-Utilice la tecla `@` para realizar una búsqueda aproximada de archivos en el proyecto.
+Utilice la tecla `@` para realizar una búsqueda aproximada de archivos dentro del proyecto.
 :::
 
 ```txt frame="none" "@packages/functions/src/api/index.ts"
-How is authentication handled in @packages/functions/src/api/index.ts
+¿Cómo se maneja la autenticación en @packages/functions/src/api/index.ts
 ```
 
-Esto es útil si hay una parte del código base en la que no trabajaste.
+Esto resulta útil cuando hay una parte del código base en la que usted no ha trabajado.
 
 ---
 
-### Agregar funciones
+### Agregar funcionalidades
 
-Puede pedirle a OpenCode que agregue nuevas funciones a su proyecto. Aunque primero recomendamos pedirle que cree un plan.
+Puede pedirle a OpenCode que agregue nuevas funcionalidades a su proyecto. Aun así, primero recomendamos pedirle que cree un plan.
 
-1. **Crea un plan**
+1. **Crear un plan**
 
-   OpenCode tiene un _Modo Plan_ que desactiva su capacidad para realizar cambios y
-   en su lugar, sugiera _cómo_ implementará la función.
+   OpenCode tiene un modo Plan que desactiva temporalmente su capacidad de hacer cambios y, en su lugar, propone _cómo_ implementará la funcionalidad.
 
-   Cambie a él usando la tecla **Tab**. Verás un indicador para esto en la esquina inferior derecha.
+   Cambie a este modo con la tecla **Tab.** Verá un indicador en la esquina inferior derecha.
 
    ```bash frame="none" title="Switch to Plan mode"
    <TAB>
    ```
 
-   Ahora describamos lo que queremos que haga.
+   Ahora describa lo que quiere que haga.
 
    ```txt frame="none"
-   When a user deletes a note, we'd like to flag it as deleted in the database.
-   Then create a screen that shows all the recently deleted notes.
-   From this screen, the user can undelete a note or permanently delete it.
+   Cuando un usuario elimine una nota, queremos marcarla como eliminada en la base de datos.
+   Luego, cree una pantalla que muestre todas las notas eliminadas recientemente.
+   Desde esa pantalla, el usuario podrá restaurar una nota o eliminarla de forma permanente.
    ```
 
-   Quiere darle a OpenCode suficientes detalles para entender lo que quiere. ayuda
-   hablar con él como si estuviera hablando con un desarrollador junior de su equipo.
+   Procure darle a OpenCode suficiente contexto para que entienda exactamente lo que necesita. Ayuda hablarle como si estuviera hablando con un desarrollador junior de su equipo.
 
    :::tip
-   Dale a OpenCode mucho contexto y ejemplos para ayudarlo a comprender lo que
-   desear.
+   Déle a OpenCode todo el contexto y los ejemplos que pueda para ayudarle a comprender lo que desea.
    :::
 
-2. **Repetir el plan**
+2. **Iterar sobre el plan**
 
-   Una vez que le proporcione un plan, puede enviarle comentarios o agregar más detalles.
+   Una vez que OpenCode le proponga un plan, puede darle comentarios o agregar más detalles.
 
    ```txt frame="none"
-   We'd like to design this new screen using a design I've used before.
-   [Image #1] Take a look at this image and use it as a reference.
+   Queremos diseñar esta nueva pantalla usando un diseño que ya hemos usado antes.
+   [Imagen #1] Revise esta imagen y úsela como referencia.
    ```
 
    :::tip
    Arrastre y suelte imágenes en la terminal para agregarlas al mensaje.
    :::
 
-   OpenCode puede escanear cualquier imagen que le proporcione y agregarla al mensaje. Puede
-   Haga esto arrastrando y soltando una imagen en la terminal.
+   OpenCode puede analizar cualquier imagen que usted le proporcione y añadirla al contexto del mensaje. Puede hacerlo arrastrando y soltando una imagen en la terminal.
 
-3. **Crea la función**
+3. **Implementar la funcionalidad**
 
-   Una vez que se sienta cómodo con el plan, vuelva al _Modo Build_
-   presionando la tecla **Tab** nuevamente.
+   Cuando esté conforme con el plan, vuelva al modo _Build_ presionando de nuevo la tecla Tab.
 
    ```bash frame="none"
    <TAB>
    ```
 
-   Y pidiéndole que haga los cambios.
+   Luego, pídale que haga los cambios.
 
    ```bash frame="none"
-   Sounds good! Go ahead and make the changes.
+   Perfecto. Continúe y realice los cambios.
    ```
 
 ---
 
 ### Realizar cambios
 
-Para cambios más sencillos, puede pedirle a OpenCode que lo construya directamente.
-sin tener que revisar el plan primero.
+Para cambios más sencillos, puede pedirle a OpenCode que los implemente directamente, sin revisar antes un plan.
 
 ```txt frame="none" "@packages/functions/src/settings.ts" "@packages/functions/src/notes.ts"
-We need to add authentication to the /settings route. Take a look at how this is
-handled in the /notes route in @packages/functions/src/notes.ts and implement
-the same logic in @packages/functions/src/settings.ts
+Necesitamos agregar autenticación a la ruta /settings. Revise cómo se maneja esto
+en la ruta /notes en @packages/functions/src/notes.ts e implemente
+la misma lógica en @packages/functions/src/settings.ts.
 ```
 
-Desea asegurarse de proporcionar una buena cantidad de detalles para que OpenCode tome la decisión correcta.
-cambios.
+Procure dar suficientes detalles para que OpenCode pueda tomar las decisiones correctas al hacer los cambios
 
 ---
 
 ### Deshacer cambios
 
-Digamos que le pides a OpenCode que haga algunos cambios.
+Supongamos que le pide a OpenCode que haga algunos cambios.
 
 ```txt frame="none" "@packages/functions/src/api/index.ts"
-Can you refactor the function in @packages/functions/src/api/index.ts?
+¿Puede refactorizar la función en @packages/functions/src/api/index.ts?
 ```
 
-Pero te das cuenta de que no es lo que querías. Puedes **deshacer** los cambios
-usando el comando `/undo`.
+Pero luego se da cuenta de que no era lo que quería. Puede **deshacer** los cambios usando el comando `/undo`.
 
 ```bash frame="none"
 /undo
 ```
 
-OpenCode ahora revertirá los cambios que realizó y mostrará su mensaje original
-de nuevo.
+OpenCode revertirá los cambios que hizo y volverá a mostrar su mensaje original.
 
 ```txt frame="none" "@packages/functions/src/api/index.ts"
-Can you refactor the function in @packages/functions/src/api/index.ts?
+¿Puede refactorizar la función en @packages/functions/src/api/index.ts?
 ```
 
-Desde aquí puedes modificar el mensaje y pedirle a OpenCode que vuelva a intentarlo.
+Desde ahí, puede modificar el mensaje y pedirle a OpenCode que lo intente de nuevo.
 
 :::tip
 Puede ejecutar `/undo` varias veces para deshacer varios cambios.
 :::
 
-O **puedes rehacer** los cambios usando el comando `/redo`.
+También puede rehacer los cambios usando el comando `/redo.`
 
 ```bash frame="none"
 /redo
@@ -335,7 +321,7 @@ O **puedes rehacer** los cambios usando el comando `/redo`.
 
 ## Compartir
 
-Las conversaciones que tengas con OpenCode pueden ser [compartidas con tu
+Las conversaciones que tenga con OpenCode pueden [compartirse con su
 equipo](/docs/share).
 
 ```bash frame="none"
@@ -348,12 +334,12 @@ Esto creará un enlace a la conversación actual y lo copiará en su portapapele
 Las conversaciones no se comparten de forma predeterminada.
 :::
 
-Aquí hay una [conversación de ejemplo](https://opencode.ai/s/4XP1fce5) con OpenCode.
+Aquí tiene una [conversación de ejemplo](https://opencode.ai/s/4XP1fce5) con OpenCode.
 
 ---
 
 ## Personalizar
 
-¡Y eso es todo! Ahora eres un profesional en el uso de OpenCode.
+Y eso es todo. Ya conoce lo básico para empezar a usar OpenCode.
 
-Para personalizarlo, recomendamos [elegir un tema](/docs/themes), [personalizar las combinaciones de teclas](/docs/keybinds), [configurar formateadores de código](/docs/formatters), [crear comandos personalizados](/docs/commands) o jugar con la [configuración OpenCode](/docs/config).
+Para personalizarlo, recomendamos [elegir un tema](/docs/themes), [personalizar las combinaciones de teclas](/docs/keybinds), [configurar formateadores de código](/docs/formatters), [crear comandos personalizados](/docs/commands) o explorar la [configuración OpenCode](/docs/config).

+ 2 - 4
packages/web/src/content/docs/zen.mdx

@@ -137,12 +137,10 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**.
 | Kimi K2 Thinking                  | $0.40  | $2.50   | -           | -            |
 | Kimi K2                           | $0.40  | $2.50   | -           | -            |
 | Qwen3 Coder 480B                  | $0.45  | $1.50   | -           | -            |
-| Claude Opus 4.6 (≤ 200K tokens)   | $5.00  | $25.00  | $0.50       | $6.25        |
-| Claude Opus 4.6 (> 200K tokens)   | $10.00 | $37.50  | $1.00       | $12.50       |
+| Claude Opus 4.6                   | $5.00  | $25.00  | $0.50       | $6.25        |
 | Claude Opus 4.5                   | $5.00  | $25.00  | $0.50       | $6.25        |
 | Claude Opus 4.1                   | $15.00 | $75.00  | $1.50       | $18.75       |
-| Claude Sonnet 4.6 (≤ 200K tokens) | $3.00  | $15.00  | $0.30       | $3.75        |
-| Claude Sonnet 4.6 (> 200K tokens) | $6.00  | $22.50  | $0.60       | $7.50        |
+| Claude Sonnet 4.6                 | $3.00  | $15.00  | $0.30       | $3.75        |
 | Claude Sonnet 4.5 (≤ 200K tokens) | $3.00  | $15.00  | $0.30       | $3.75        |
 | Claude Sonnet 4.5 (> 200K tokens) | $6.00  | $22.50  | $0.60       | $7.50        |
 | Claude Sonnet 4 (≤ 200K tokens)   | $3.00  | $15.00  | $0.30       | $3.75        |

+ 1 - 1
sdks/vscode/package.json

@@ -2,7 +2,7 @@
   "name": "opencode",
   "displayName": "opencode",
   "description": "opencode for VS Code",
-  "version": "1.2.26",
+  "version": "1.2.27",
   "publisher": "sst-dev",
   "repository": {
     "type": "git",