Compare commits

..
Author SHA1 Message Date
Aiden Cline 593e89b4f4 vscode: fix script
publish-vscode / publish (push) Has been cancelled
2025-10-22 17:11:51 -05:00
Aiden Cline 4d3f703715 vscode: adjust tsconfig
publish-vscode / publish (push) Has been cancelled
2025-10-22 16:43:06 -05:00
Aiden Cline 123dcc10cc ignore: cleanup bun.lock w/ bun i 2025-10-22 15:34:41 -05:00
Dax Raad 28d8af48a0 add parent id to assistant messages
publish-vscode / publish (push) Has been cancelled
2025-10-22 15:01:13 -04:00
Kyle GalbraithandGitHub 10ff6e9830 docs: fix typo in SDK documentation (#3355) 2025-10-22 11:43:28 -05:00
theVinchiandGitHub a7b43d82ab add Amazon Nova models to us-* prefix requirement list (#3357) 2025-10-22 11:41:38 -05:00
Aiden Cline 9005fd31ed tweak 2025-10-22 11:29:10 -05:00
Aiden Cline d2bded23c3 tweak 2025-10-22 11:28:06 -05:00
Aiden Cline c0cbc37f85 tweak: model priority 2025-10-22 11:12:32 -05:00
Aiden Cline 9df61055e2 change default title model 2025-10-22 10:41:08 -05:00
GitHub Action 074136b1e8 ignore: update download stats 2025-10-22 2025-10-22 12:04:58 +00:00
Affaan MustafaGitHubopencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>rekram1-node
8db5951287 feat: Improve editor detection with auto-discovery and better error messages (#3155)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
2025-10-21 20:49:42 -05:00
Haris GušićandGitHub 97c7e941eb fix: opencode run shouldn't print to stderr (#3341) 2025-10-21 19:53:09 -05:00
Aiden ClineandGitHub 354f5c3281 ignore: fix acp test (#3339) 2025-10-21 15:27:59 -05:00
18 changed files with 109 additions and 61 deletions
+4
View File
@@ -24,6 +24,10 @@ jobs:
- run: git fetch --force --tags
- run: bun install -g @vscode/vsce
- name: Install extension dependencies
run: bun install
working-directory: ./sdks/vscode
- name: Publish
run: |
./script/publish
+1
View File
@@ -115,3 +115,4 @@
| 2025-10-19 | 536,209 (+4,645) | 469,078 (+3,806) | 1,005,287 (+8,451) |
| 2025-10-20 | 541,264 (+5,055) | 472,952 (+3,874) | 1,014,216 (+8,929) |
| 2025-10-21 | 548,721 (+7,457) | 479,703 (+6,751) | 1,028,424 (+14,208) |
| 2025-10-22 | 557,949 (+9,228) | 491,395 (+11,692) | 1,049,344 (+20,920) |
+2 -1
View File
@@ -1,3 +1,4 @@
import { EOL } from "os"
import { File } from "../../../file"
import { bootstrap } from "../../bootstrap"
import { cmd } from "../cmd"
@@ -13,7 +14,7 @@ const FileSearchCommand = cmd({
async handler(args) {
await bootstrap(process.cwd(), async () => {
const results = await File.search({ query: args.query })
console.log(results.join("\n"))
console.log(results.join(EOL))
})
},
})
@@ -1,3 +1,4 @@
import { EOL } from "os"
import { Ripgrep } from "../../../file/ripgrep"
import { Instance } from "../../../project/instance"
import { bootstrap } from "../../bootstrap"
@@ -48,7 +49,7 @@ const FilesCommand = cmd({
files.push(file)
if (args.limit && files.length >= args.limit) break
}
console.log(files.join("\n"))
console.log(files.join(EOL))
})
},
})
+2 -1
View File
@@ -4,6 +4,7 @@ import { cmd } from "./cmd"
import { bootstrap } from "../bootstrap"
import { UI } from "../ui"
import * as prompts from "@clack/prompts"
import { EOL } from "os"
export const ExportCommand = cmd({
command: "export [sessionID]",
@@ -67,7 +68,7 @@ export const ExportCommand = cmd({
}
process.stdout.write(JSON.stringify(exportData, null, 2))
process.stdout.write("\n")
process.stdout.write(EOL)
} catch (error) {
UI.error(`Session not found: ${sessionID!}`)
process.exit(1)
+9 -19
View File
@@ -13,6 +13,7 @@ import { Identifier } from "../../id/id"
import { Agent } from "../../agent/agent"
import { Command } from "../../command"
import { SessionPrompt } from "../../session/prompt"
import { EOL } from "os"
const TOOL: Record<string, [string, string]> = {
todowrite: ["Todo", UI.Style.TEXT_WARNING_BOLD],
@@ -194,13 +195,12 @@ export const RunCommand = cmd({
sessionID: session?.id,
...data,
}
process.stdout.write(JSON.stringify(jsonEvent) + "\n")
process.stdout.write(JSON.stringify(jsonEvent) + EOL)
return true
}
return false
}
let text = ""
const messageID = Identifier.ascending("message")
Bus.subscribe(MessageV2.Event.PartUpdated, async (evt) => {
@@ -232,15 +232,14 @@ export const RunCommand = cmd({
}
if (part.type === "text") {
text = part.text
const text = part.text
const isPiped = !process.stdout.isTTY
if (part.time?.end) {
if (outputJsonEvent("text", { part })) return
UI.empty()
UI.println(UI.markdown(text))
UI.empty()
text = ""
return
if (!isPiped) UI.println()
process.stdout.write((isPiped ? text : UI.markdown(text)) + EOL)
if (!isPiped) UI.println()
}
}
})
@@ -254,13 +253,13 @@ export const RunCommand = cmd({
if ("data" in error && error.data && "message" in error.data) {
err = error.data.message
}
errorMsg = errorMsg ? errorMsg + "\n" + err : err
errorMsg = errorMsg ? errorMsg + EOL + err : err
if (outputJsonEvent("error", { error })) return
UI.error(err)
})
const result = await (async () => {
await (async () => {
if (args.command) {
return await SessionPrompt.command({
messageID,
@@ -289,15 +288,6 @@ export const RunCommand = cmd({
],
})
})()
const isPiped = !process.stdout.isTTY
if (isPiped) {
const match = result.parts.findLast((x: any) => x.type === "text") as any
if (outputJsonEvent("text", { text: match })) return
if (match) process.stdout.write(UI.markdown(match.text))
if (errorMsg) process.stdout.write(errorMsg)
}
UI.empty()
if (errorMsg) process.exit(1)
})
},
+2 -1
View File
@@ -20,6 +20,7 @@ import { GithubCommand } from "./cli/cmd/github"
import { ExportCommand } from "./cli/cmd/export"
import { AttachCommand } from "./cli/cmd/attach"
import { AcpCommand } from "./cli/cmd/acp"
import { EOL } from "os"
const cancel = new AbortController()
@@ -130,7 +131,7 @@ try {
const formatted = FormatError(e)
if (formatted) UI.error(formatted)
if (formatted === undefined) {
UI.error("Unexpected error, check log file at " + Log.file() + " for more details\n")
UI.error("Unexpected error, check log file at " + Log.file() + " for more details" + EOL)
console.error(e)
}
process.exitCode = 1
+36 -21
View File
@@ -95,7 +95,14 @@ export namespace Provider {
switch (regionPrefix) {
case "us": {
const modelRequiresPrefix = ["claude", "deepseek"].some((m) => modelID.includes(m))
const modelRequiresPrefix = [
"nova-micro",
"nova-lite",
"nova-pro",
"nova-premier",
"claude",
"deepseek"
].some((m) => modelID.includes(m))
const isGovCloud = region.startsWith("us-gov")
if (modelRequiresPrefix && !isGovCloud) {
modelID = `${regionPrefix}.${modelID}`
@@ -121,9 +128,10 @@ export namespace Provider {
}
case "ap": {
const isAustraliaRegion = ["ap-southeast-2", "ap-southeast-4"].includes(region)
if (isAustraliaRegion && ["anthropic.claude-sonnet-4-5", "anthropic.claude-haiku"].some((m) =>
modelID.includes(m),
)) {
if (
isAustraliaRegion &&
["anthropic.claude-sonnet-4-5", "anthropic.claude-haiku"].some((m) => modelID.includes(m))
) {
regionPrefix = "au"
modelID = `${regionPrefix}.${modelID}`
} else {
@@ -273,31 +281,31 @@ export namespace Provider {
cost:
!model.cost && !existing?.cost
? {
input: 0,
output: 0,
cache_read: 0,
cache_write: 0,
}
input: 0,
output: 0,
cache_read: 0,
cache_write: 0,
}
: {
cache_read: 0,
cache_write: 0,
...existing?.cost,
...model.cost,
},
cache_read: 0,
cache_write: 0,
...existing?.cost,
...model.cost,
},
options: {
...existing?.options,
...model.options,
},
limit: model.limit ??
existing?.limit ?? {
context: 0,
output: 0,
},
context: 0,
output: 0,
},
modalities: model.modalities ??
existing?.modalities ?? {
input: ["text"],
output: ["text"],
},
input: ["text"],
output: ["text"],
},
provider: model.provider ?? existing?.provider,
}
if (model.id && model.id !== modelID) {
@@ -509,7 +517,14 @@ export namespace Provider {
const provider = await state().then((state) => state.providers[providerID])
if (!provider) return
const priority = ["3-5-haiku", "3.5-haiku", "gemini-2.5-flash", "gpt-5-nano"]
const priority = [
"claude-haiku-4-5",
"claude-haiku-4.5",
"3-5-haiku",
"3.5-haiku",
"gemini-2.5-flash",
"gpt-5-nano",
]
for (const item of priority) {
for (const model of Object.keys(provider.info.models)) {
if (model.includes(item)) return getModel(providerID, model)
@@ -109,6 +109,7 @@ export namespace SessionCompaction {
const msg = (await Session.updateMessage({
id: Identifier.ascending("message"),
role: "assistant",
parentID: toSummarize.findLast((m) => m.info.role === "user")?.info.id!,
sessionID: input.sessionID,
system,
mode: "build",
@@ -278,6 +278,7 @@ export namespace MessageV2 {
])
.optional(),
system: z.string().array(),
parentID: z.string(),
modelID: z.string(),
providerID: z.string(),
mode: z.string(),
@@ -346,6 +347,7 @@ export namespace MessageV2 {
if (v1.role === "assistant") {
const info: Assistant = {
id: v1.id,
parentID: "",
sessionID: v1.metadata.sessionID,
role: "assistant",
time: {
+7 -4
View File
@@ -235,7 +235,7 @@ export namespace SessionPrompt {
modelID: model.info.id,
})
step++
await processor.next()
await processor.next(msgs.findLast((m) => m.info.role === "user")?.info.id!)
await using _ = defer(async () => {
await processor.end()
})
@@ -900,9 +900,10 @@ export namespace SessionPrompt {
let snapshot: string | undefined
let blocked = false
async function createMessage() {
async function createMessage(parentID: string) {
const msg: MessageV2.Info = {
id: Identifier.ascending("message"),
parentID,
role: "assistant",
system: input.system,
mode: input.agent,
@@ -938,11 +939,11 @@ export namespace SessionPrompt {
assistantMsg = undefined
}
},
async next() {
async next(parentID: string) {
if (assistantMsg) {
throw new Error("end previous assistant message first")
}
assistantMsg = await createMessage()
assistantMsg = await createMessage(parentID)
return assistantMsg
},
get message() {
@@ -1424,6 +1425,7 @@ export namespace SessionPrompt {
const msg: MessageV2.Assistant = {
id: Identifier.ascending("message"),
sessionID: input.sessionID,
parentID: userMsg.id,
system: [],
mode: input.agent,
cost: 0,
@@ -1696,6 +1698,7 @@ export namespace SessionPrompt {
const assistantMsg: MessageV2.Assistant = {
id: Identifier.ascending("message"),
sessionID: input.sessionID,
parentID: userMsg.id,
system: [],
mode: agentName,
cost: 0,
+5 -2
View File
@@ -3,7 +3,7 @@ import { spawn } from "child_process"
describe("ACP Server", () => {
test("initialize and shutdown", async () => {
const proc = spawn("bun", ["run", "--conditions=development", "src/index.ts", "acp"], {
const proc = spawn("bun", ["run", "dev", "acp"], {
cwd: process.cwd(),
stdio: ["pipe", "pipe", "pipe"],
env: { ...process.env, OPENCODE: "1" },
@@ -27,6 +27,9 @@ describe("ACP Server", () => {
}
})
// Wait for server to be ready
await new Promise((resolve) => setTimeout(resolve, 500))
proc.stdin.write(
encoder.encode(
JSON.stringify({
@@ -48,7 +51,7 @@ describe("ACP Server", () => {
}, 10000)
test("create session", async () => {
const proc = spawn("bun", ["run", "--conditions=development", "src/index.ts", "acp"], {
const proc = spawn("bun", ["run", "dev", "acp"], {
cwd: process.cwd(),
stdio: ["pipe", "pipe", "pipe"],
env: { ...process.env, OPENCODE: "1" },
+4 -5
View File
@@ -1158,9 +1158,9 @@ func (a Model) executeCommand(command commands.Command) (tea.Model, tea.Cmd) {
// status.Warn("Agent is working, please wait...")
return a, nil
}
editor := os.Getenv("EDITOR")
editor := util.GetEditor()
if editor == "" {
return a, toast.NewErrorToast("No EDITOR set, can't open editor")
return a, toast.NewErrorToast("No editor found. Set EDITOR environment variable (e.g., export EDITOR=vim)")
}
value := a.editor.Value()
@@ -1404,10 +1404,9 @@ func (a Model) executeCommand(command commands.Command) (tea.Model, tea.Cmd) {
// Format to Markdown
markdownContent := formatConversationToMarkdown(messages)
// Check if EDITOR is set
editor := os.Getenv("EDITOR")
editor := util.GetEditor()
if editor == "" {
return a, toast.NewErrorToast("No EDITOR set, can't open editor")
return a, toast.NewErrorToast("No editor found. Set EDITOR environment variable (e.g., export EDITOR=vim)")
}
// Create and write to temp file
+24
View File
@@ -3,6 +3,8 @@ package util
import (
"log/slog"
"os"
"os/exec"
"runtime"
"strings"
"time"
@@ -45,3 +47,25 @@ func Measure(tag string) func(...any) {
slog.Debug(tag, args...)
}
}
func GetEditor() string {
if editor := os.Getenv("VISUAL"); editor != "" {
return editor
}
if editor := os.Getenv("EDITOR"); editor != "" {
return editor
}
commonEditors := []string{"vim", "nvim", "zed", "code", "cursor", "vi", "nano"}
if runtime.GOOS == "windows" {
commonEditors = []string{"vim", "nvim", "zed", "code.cmd", "cursor.cmd", "notepad.exe", "vi", "nano"}
}
for _, editor := range commonEditors {
if _, err := exec.LookPath(editor); err == nil {
return editor
}
}
return ""
}
+1 -1
View File
@@ -69,7 +69,7 @@ opencode.server.close()
## Client only
If you aready have a running instance of opencode, you can create a client instance to connect to it:
If you already have a running instance of opencode, you can create a client instance to connect to it:
```javascript
import { createOpencodeClient } from "@opencode-ai/sdk"
+1 -1
View File
@@ -6,7 +6,7 @@
"devDependencies": {
"@types/mocha": "^10.0.10",
"@types/node": "20.x",
"@types/vscode": "^1.102.0",
"@types/vscode": "^1.94.0",
"@typescript-eslint/eslint-plugin": "^8.31.1",
"@typescript-eslint/parser": "^8.31.1",
"@vscode/test-cli": "^0.0.11",
+4 -3
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail
# Get the latest Git tag
latest_tag=$(git tag --sort=committerdate | grep -E '^vscode-v[0-9]+\.[0-9]+\.[0-9]+$' | tail -1)
@@ -7,14 +8,14 @@ if [ -z "$latest_tag" ]; then
exit 1
fi
echo "Latest tag: $latest_tag"
version=$(echo $latest_tag | sed 's/^vscode-v//')
version=$(echo "$latest_tag" | sed 's/^vscode-v//')
echo "Latest version: $version"
# package-marketplace
vsce package --no-git-tag-version --no-update-package-json --no-dependencies --skip-license -o dist/opencode.vsix $version
vsce package --no-git-tag-version --no-update-package-json --no-dependencies --skip-license -o dist/opencode.vsix "$version"
# publish-marketplace
vsce publish --packagePath dist/opencode.vsix
# publish-openvsx
npx ovsx publish dist/opencode.vsix -p $OPENVSX_TOKEN
npx ovsx publish dist/opencode.vsix -p "$OPENVSX_TOKEN"
+2 -1
View File
@@ -6,7 +6,8 @@
"sourceMap": true,
"rootDir": "src",
"typeRoots": ["./node_modules/@types"],
"strict": true /* enable all strict type-checking options */
"strict": true /* enable all strict type-checking options */,
"skipLibCheck": true
/* Additional Checks */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */