Dax Raad 8 месяцев назад
Родитель
Сommit
0e035b3115

+ 10 - 1
packages/opencode/AGENTS.md

@@ -16,9 +16,19 @@
 - **Naming**: camelCase for variables/functions, PascalCase for classes/namespaces
 - **Naming**: camelCase for variables/functions, PascalCase for classes/namespaces
 - **Error handling**: Use Result patterns, avoid throwing exceptions in tools
 - **Error handling**: Use Result patterns, avoid throwing exceptions in tools
 - **File structure**: Namespace-based organization (e.g., `Tool.define()`, `Session.create()`)
 - **File structure**: Namespace-based organization (e.g., `Tool.define()`, `Session.create()`)
+
+## IMPORTANT
+
+- Try to keep things in one function unless composable or reusable
 - DO NOT do unnecessary destructuring of variables
 - DO NOT do unnecessary destructuring of variables
 - DO NOT use else statements unless necessary
 - DO NOT use else statements unless necessary
 - DO NOT use try catch if it can be avoided
 - DO NOT use try catch if it can be avoided
+- AVOID try catch where possible
+- AVOID else statements
+- AVOID using `any` type
+- AVOID let statements
+- PREFER single word variable names where possible
+- Use as many bun apis as possible like Bun.file()
 
 
 ## Architecture
 ## Architecture
 
 
@@ -27,4 +37,3 @@
 - **Validation**: All inputs validated with Zod schemas
 - **Validation**: All inputs validated with Zod schemas
 - **Logging**: Use `Log.create({ service: "name" })` pattern
 - **Logging**: Use `Log.create({ service: "name" })` pattern
 - **Storage**: Use `Storage` namespace for persistence
 - **Storage**: Use `Storage` namespace for persistence
-

+ 3 - 0
packages/opencode/src/cli/cmd/run.ts

@@ -115,6 +115,9 @@ export const RunCommand = {
         })
         })
 
 
         const { providerID, modelID } = await Provider.defaultModel()
         const { providerID, modelID } = await Provider.defaultModel()
+        setTimeout(() => {
+          Session.abort(session.id)
+        }, 8000)
         await Session.chat({
         await Session.chat({
           sessionID: session.id,
           sessionID: session.id,
           providerID,
           providerID,

+ 0 - 3
packages/opencode/src/index.ts

@@ -3,11 +3,8 @@ import { App } from "./app/app"
 import { Server } from "./server/server"
 import { Server } from "./server/server"
 import fs from "fs/promises"
 import fs from "fs/promises"
 import path from "path"
 import path from "path"
-
 import { Share } from "./share/share"
 import { Share } from "./share/share"
-
 import { Global } from "./global"
 import { Global } from "./global"
-
 import yargs from "yargs"
 import yargs from "yargs"
 import { hideBin } from "yargs/helpers"
 import { hideBin } from "yargs/helpers"
 import { RunCommand } from "./cli/cmd/run"
 import { RunCommand } from "./cli/cmd/run"

+ 114 - 83
packages/opencode/src/session/index.ts

@@ -405,7 +405,7 @@ export namespace Session {
         await updateMessage(next)
         await updateMessage(next)
       },
       },
       onError(err) {
       onError(err) {
-        log.error("error", err)
+        log.error("callback error", err)
         switch (true) {
         switch (true) {
           case LoadAPIKeyError.isInstance(err.error):
           case LoadAPIKeyError.isInstance(err.error):
             next.metadata.error = new Provider.AuthError(
             next.metadata.error = new Provider.AuthError(
@@ -460,100 +460,131 @@ export namespace Session {
       },
       },
       model: model.language,
       model: model.language,
     })
     })
-    for await (const value of result.fullStream) {
-      l.info("part", {
-        type: value.type,
-      })
-      switch (value.type) {
-        case "step-start":
-          next.parts.push({
-            type: "step-start",
-          })
-          break
-        case "text-delta":
-          if (!text) {
-            text = {
-              type: "text",
-              text: value.textDelta,
-            }
-            next.parts.push(text)
+    try {
+      for await (const value of result.fullStream) {
+        l.info("part", {
+          type: value.type,
+        })
+        switch (value.type) {
+          case "step-start":
+            next.parts.push({
+              type: "step-start",
+            })
+            break
+          case "text-delta":
+            if (!text) {
+              text = {
+                type: "text",
+                text: value.textDelta,
+              }
+              next.parts.push(text)
+              break
+            } else text.text += value.textDelta
             break
             break
-          } else text.text += value.textDelta
-          break
-
-        case "tool-call": {
-          const [match] = next.parts.flatMap((p) =>
-            p.type === "tool-invocation" &&
-            p.toolInvocation.toolCallId === value.toolCallId
-              ? [p]
-              : [],
-          )
-          if (!match) break
-          match.toolInvocation.args = value.args
-          match.toolInvocation.state = "call"
-          Bus.publish(Message.Event.PartUpdated, {
-            part: match,
-            messageID: next.id,
-            sessionID: next.metadata.sessionID,
-          })
-          break
-        }
-
-        case "tool-call-streaming-start":
-          next.parts.push({
-            type: "tool-invocation",
-            toolInvocation: {
-              state: "partial-call",
-              toolName: value.toolName,
-              toolCallId: value.toolCallId,
-              args: {},
-            },
-          })
-          Bus.publish(Message.Event.PartUpdated, {
-            part: next.parts[next.parts.length - 1],
-            messageID: next.id,
-            sessionID: next.metadata.sessionID,
-          })
-          break
-
-        case "tool-call-delta":
-          break
 
 
-        // for some reason ai sdk claims to not send this part but it does
-        // @ts-expect-error
-        case "tool-result":
-          const match = next.parts.find(
-            (p) =>
+          case "tool-call": {
+            const [match] = next.parts.flatMap((p) =>
               p.type === "tool-invocation" &&
               p.type === "tool-invocation" &&
-              // @ts-expect-error
-              p.toolInvocation.toolCallId === value.toolCallId,
-          )
-          if (match && match.type === "tool-invocation") {
-            match.toolInvocation = {
-              // @ts-expect-error
-              args: value.args,
-              // @ts-expect-error
-              toolCallId: value.toolCallId,
-              // @ts-expect-error
-              toolName: value.toolName,
-              state: "result",
-              // @ts-expect-error
-              result: value.result as string,
-            }
+              p.toolInvocation.toolCallId === value.toolCallId
+                ? [p]
+                : [],
+            )
+            if (!match) break
+            match.toolInvocation.args = value.args
+            match.toolInvocation.state = "call"
             Bus.publish(Message.Event.PartUpdated, {
             Bus.publish(Message.Event.PartUpdated, {
               part: match,
               part: match,
               messageID: next.id,
               messageID: next.id,
               sessionID: next.metadata.sessionID,
               sessionID: next.metadata.sessionID,
             })
             })
+            break
           }
           }
-          break
 
 
+          case "tool-call-streaming-start":
+            next.parts.push({
+              type: "tool-invocation",
+              toolInvocation: {
+                state: "partial-call",
+                toolName: value.toolName,
+                toolCallId: value.toolCallId,
+                args: {},
+              },
+            })
+            Bus.publish(Message.Event.PartUpdated, {
+              part: next.parts[next.parts.length - 1],
+              messageID: next.id,
+              sessionID: next.metadata.sessionID,
+            })
+            break
+
+          case "tool-call-delta":
+            break
+
+          // for some reason ai sdk claims to not send this part but it does
+          // @ts-expect-error
+          case "tool-result":
+            const match = next.parts.find(
+              (p) =>
+                p.type === "tool-invocation" &&
+                // @ts-expect-error
+                p.toolInvocation.toolCallId === value.toolCallId,
+            )
+            if (match && match.type === "tool-invocation") {
+              match.toolInvocation = {
+                // @ts-expect-error
+                args: value.args,
+                // @ts-expect-error
+                toolCallId: value.toolCallId,
+                // @ts-expect-error
+                toolName: value.toolName,
+                state: "result",
+                // @ts-expect-error
+                result: value.result as string,
+              }
+              Bus.publish(Message.Event.PartUpdated, {
+                part: match,
+                messageID: next.id,
+                sessionID: next.metadata.sessionID,
+              })
+            }
+            break
+
+          default:
+            l.info("unhandled", {
+              type: value.type,
+            })
+        }
+        await updateMessage(next)
+      }
+    } catch (e: any) {
+      log.error("stream error", {
+        error: e,
+      })
+      switch (true) {
+        case LoadAPIKeyError.isInstance(e):
+          next.metadata.error = new Provider.AuthError(
+            {
+              providerID: input.providerID,
+              message: e.message,
+            },
+            { cause: e },
+          ).toObject()
+          break
+        case e instanceof Error:
+          next.metadata.error = new NamedError.Unknown(
+            { message: e.toString() },
+            { cause: e },
+          ).toObject()
+          break
         default:
         default:
-          l.info("unhandled", {
-            type: value.type,
-          })
+          next.metadata.error = new NamedError.Unknown(
+            { message: JSON.stringify(e) },
+            { cause: e },
+          )
       }
       }
-      await updateMessage(next)
+      Bus.publish(Event.Error, {
+        error: next.metadata.error,
+      })
     }
     }
     next.metadata!.time.completed = Date.now()
     next.metadata!.time.completed = Date.now()
     for (const part of next.parts) {
     for (const part of next.parts) {