summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDax Raad <[email protected]>2025-06-14 21:23:57 -0400
committerDax Raad <[email protected]>2025-06-14 21:23:57 -0400
commit0e035b3115d35b0a2fc8972375052f2b2f893fb2 (patch)
tree747f65169f08f7d3efd48afc52b5216a514b6d48
parentb855511d9a3415cec3620e61f3691911bd21151c (diff)
downloadopencode-0e035b3115d35b0a2fc8972375052f2b2f893fb2.tar.gz
opencode-0e035b3115d35b0a2fc8972375052f2b2f893fb2.zip
fix aborting issue
-rw-r--r--packages/opencode/AGENTS.md11
-rw-r--r--packages/opencode/src/cli/cmd/run.ts3
-rw-r--r--packages/opencode/src/index.ts3
-rw-r--r--packages/opencode/src/session/index.ts197
4 files changed, 127 insertions, 87 deletions
diff --git a/packages/opencode/AGENTS.md b/packages/opencode/AGENTS.md
index d016d560a..b5bf02915 100644
--- a/packages/opencode/AGENTS.md
+++ b/packages/opencode/AGENTS.md
@@ -16,9 +16,19 @@
- **Naming**: camelCase for variables/functions, PascalCase for classes/namespaces
- **Error handling**: Use Result patterns, avoid throwing exceptions in tools
- **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 use else statements unless necessary
- 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
@@ -27,4 +37,3 @@
- **Validation**: All inputs validated with Zod schemas
- **Logging**: Use `Log.create({ service: "name" })` pattern
- **Storage**: Use `Storage` namespace for persistence
-
diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts
index 3a4677484..dd9db3cc9 100644
--- a/packages/opencode/src/cli/cmd/run.ts
+++ b/packages/opencode/src/cli/cmd/run.ts
@@ -115,6 +115,9 @@ export const RunCommand = {
})
const { providerID, modelID } = await Provider.defaultModel()
+ setTimeout(() => {
+ Session.abort(session.id)
+ }, 8000)
await Session.chat({
sessionID: session.id,
providerID,
diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts
index 4595c3689..74f50e343 100644
--- a/packages/opencode/src/index.ts
+++ b/packages/opencode/src/index.ts
@@ -3,11 +3,8 @@ import { App } from "./app/app"
import { Server } from "./server/server"
import fs from "fs/promises"
import path from "path"
-
import { Share } from "./share/share"
-
import { Global } from "./global"
-
import yargs from "yargs"
import { hideBin } from "yargs/helpers"
import { RunCommand } from "./cli/cmd/run"
diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts
index 850c83c4c..cb3d6c89e 100644
--- a/packages/opencode/src/session/index.ts
+++ b/packages/opencode/src/session/index.ts
@@ -405,7 +405,7 @@ export namespace Session {
await updateMessage(next)
},
onError(err) {
- log.error("error", err)
+ log.error("callback error", err)
switch (true) {
case LoadAPIKeyError.isInstance(err.error):
next.metadata.error = new Provider.AuthError(
@@ -460,100 +460,131 @@ export namespace Session {
},
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
- } 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" &&
- // @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, {
part: match,
messageID: next.id,
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:
- 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()
for (const part of next.parts) {