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

ACP: update package, fix slash command bug (#3906)

Aiden Cline 3 месяцев назад
Родитель
Сommit
1e0596bc46

+ 2 - 2
bun.lock

@@ -173,7 +173,7 @@
       "dependencies": {
         "@actions/core": "1.11.1",
         "@actions/github": "6.0.1",
-        "@agentclientprotocol/sdk": "0.4.9",
+        "@agentclientprotocol/sdk": "0.5.1",
         "@clack/prompts": "1.0.0-alpha.1",
         "@hono/standard-validator": "0.1.5",
         "@hono/zod-validator": "catalog:",
@@ -401,7 +401,7 @@
 
     "@adobe/css-tools": ["@adobe/[email protected]", "", {}, "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg=="],
 
-    "@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.4.9", "", { "dependencies": { "zod": "^3.0.0" } }, "sha512-ExwH828LaTGoTTjxuw49l+fwOLA+Yx0+qkWn1TcHMOsY5mVI9CUfkj7ZDhv2klgZ7mJeT+lxX/Dn/KINv1AkNQ=="],
+    "@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.5.1", "", { "dependencies": { "zod": "^3.0.0" } }, "sha512-9bq2TgjhLBSUSC5jE04MEe+Hqw8YePzKghhYZ9QcjOyonY3q2oJfX6GoSO83hURpEnsqEPIrex6VZN3+61fBJg=="],
 
     "@ai-sdk/amazon-bedrock": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-icLGO7Q0NinnHIPgT+y1QjHVwH4HwV+brWbvM+FfCG2Afpa89PyKa3Ret91kGjZpBgM/xnj1B7K5eM+rRlsXQA=="],
 

+ 2 - 2
packages/opencode/package.json

@@ -43,7 +43,7 @@
   "dependencies": {
     "@actions/core": "1.11.1",
     "@actions/github": "6.0.1",
-    "@agentclientprotocol/sdk": "0.4.9",
+    "@agentclientprotocol/sdk": "0.5.1",
     "@clack/prompts": "1.0.0-alpha.1",
     "@hono/standard-validator": "0.1.5",
     "@hono/zod-validator": "catalog:",
@@ -57,8 +57,8 @@
     "@opentui/core": "0.1.33",
     "@opentui/solid": "0.1.33",
     "@parcel/watcher": "2.5.1",
-    "@solid-primitives/event-bus": "1.1.2",
     "@pierre/precision-diffs": "catalog:",
+    "@solid-primitives/event-bus": "1.1.2",
     "@standard-schema/spec": "1.0.0",
     "@zip.js/zip.js": "2.7.62",
     "ai": "catalog:",

+ 59 - 29
packages/opencode/src/acp/agent.ts

@@ -1,9 +1,12 @@
 import {
+  RequestError,
   type Agent as ACPAgent,
   type AgentSideConnection,
   type AuthenticateRequest,
+  type AuthMethod,
   type CancelNotification,
   type InitializeRequest,
+  type InitializeResponse,
   type LoadSessionRequest,
   type NewSessionRequest,
   type PermissionOption,
@@ -33,6 +36,7 @@ import type { Config } from "@/config/config"
 import { MCP } from "@/mcp"
 import { Todo } from "@/session/todo"
 import { z } from "zod"
+import { LoadAPIKeyError } from "ai"
 
 export namespace ACP {
   const log = Log.create({ service: "acp-agent" })
@@ -302,9 +306,26 @@ export namespace ACP {
       })
     }
 
-    async initialize(params: InitializeRequest) {
+    async initialize(params: InitializeRequest): Promise<InitializeResponse> {
       log.info("initialize", { protocolVersion: params.protocolVersion })
 
+      const authMethod: AuthMethod = {
+        description: "Run `opencode auth login` in the terminal",
+        name: "Login with opencode",
+        id: "opencode-login",
+      }
+
+      // If client supports terminal-auth capability, use that instead.
+      if (params.clientCapabilities?._meta?.["terminal-auth"] === true) {
+        authMethod._meta = {
+          "terminal-auth": {
+            command: "opencode",
+            args: ["auth", "login"],
+            label: "OpenCode Login",
+          },
+        }
+      }
+
       return {
         protocolVersion: 1,
         agentCapabilities: {
@@ -325,10 +346,9 @@ export namespace ACP {
             id: "opencode-login",
           },
         ],
-        _meta: {
-          opencode: {
-            version: Installation.VERSION,
-          },
+        agentInfo: {
+          name: "OpenCode",
+          version: Installation.VERSION,
         },
       }
     }
@@ -338,21 +358,31 @@ export namespace ACP {
     }
 
     async newSession(params: NewSessionRequest) {
-      const model = await defaultModel(this.config)
-      const session = await this.sessionManager.create(params.cwd, params.mcpServers, model)
-
-      log.info("creating_session", { mcpServers: params.mcpServers.length })
-      const load = await this.loadSession({
-        cwd: params.cwd,
-        mcpServers: params.mcpServers,
-        sessionId: session.id,
-      })
+      try {
+        const model = await defaultModel(this.config)
+        const session = await this.sessionManager.create(params.cwd, params.mcpServers, model)
+
+        log.info("creating_session", { mcpServers: params.mcpServers.length })
+        const load = await this.loadSession({
+          cwd: params.cwd,
+          mcpServers: params.mcpServers,
+          sessionId: session.id,
+        })
 
-      return {
-        sessionId: session.id,
-        models: load.models,
-        modes: load.modes,
-        _meta: {},
+        return {
+          sessionId: session.id,
+          models: load.models,
+          modes: load.modes,
+          _meta: {},
+        }
+      } catch (e) {
+        const error = MessageV2.fromError(e, {
+          providerID: this.config.defaultModel?.providerID ?? "unknown",
+        })
+        if (LoadAPIKeyError.isInstance(error)) {
+          throw RequestError.authRequired()
+        }
+        throw e
       }
     }
 
@@ -387,16 +417,6 @@ export namespace ACP {
           description: "compact the session",
         })
 
-      setTimeout(() => {
-        this.connection.sessionUpdate({
-          sessionId,
-          update: {
-            sessionUpdate: "available_commands_update",
-            availableCommands,
-          },
-        })
-      }, 0)
-
       const availableModes = (await Agents.list())
         .filter((agent) => agent.mode !== "subagent")
         .map((agent) => ({
@@ -437,6 +457,16 @@ export namespace ACP {
         }),
       )
 
+      setTimeout(() => {
+        this.connection.sessionUpdate({
+          sessionId,
+          update: {
+            sessionUpdate: "available_commands_update",
+            availableCommands,
+          },
+        })
+      }, 0)
+
       return {
         sessionId,
         models: {

+ 6 - 1
packages/opencode/src/acp/session.ts

@@ -6,13 +6,18 @@ import type { ACPSessionState } from "./types"
 export class ACPSessionManager {
   private sessions = new Map<string, ACPSessionState>()
 
-  async create(cwd: string, mcpServers: McpServer[], model?: ACPSessionState["model"]): Promise<ACPSessionState> {
+  async create(
+    cwd: string,
+    mcpServers: McpServer[],
+    model?: ACPSessionState["model"],
+  ): Promise<ACPSessionState> {
     const session = await Session.create({ title: `ACP Session ${crypto.randomUUID()}` })
     const sessionId = session.id
     const resolvedModel = model ?? (await Provider.defaultModel())
 
     const state: ACPSessionState = {
       id: sessionId,
+      parentId: session.parentID,
       cwd,
       mcpServers,
       createdAt: new Date(),

+ 1 - 0
packages/opencode/src/acp/types.ts

@@ -2,6 +2,7 @@ import type { McpServer } from "@agentclientprotocol/sdk"
 
 export interface ACPSessionState {
   id: string
+  parentId?: string
   cwd: string
   mcpServers: McpServer[]
   createdAt: Date

+ 18 - 2
packages/opencode/src/mcp/index.ts

@@ -77,7 +77,15 @@ export namespace MCP {
       }
     },
     async (state) => {
-      await Promise.all(Object.values(state.clients).map((client) => client.close()))
+      await Promise.all(
+        Object.values(state.clients).map((client) =>
+          client.close().catch((error) => {
+            log.error("Failed to close MCP client", {
+              error,
+            })
+          }),
+        ),
+      )
     },
   )
 
@@ -201,7 +209,15 @@ export namespace MCP {
 
     const result = await withTimeout(mcpClient.tools(), mcp.timeout ?? 5000).catch(() => {})
     if (!result) {
-      await mcpClient.close()
+      await mcpClient.close().catch((error) => {
+        log.error("Failed to close MCP client", {
+          error,
+        })
+      })
+      status = {
+        status: "failed",
+        error: "Failed to get tools",
+      }
       return {
         mcpClient: undefined,
         status: {