Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
593e89b4f4 | ||
|
|
4d3f703715 | ||
|
|
123dcc10cc | ||
|
|
28d8af48a0 | ||
|
|
10ff6e9830 | ||
|
|
a7b43d82ab | ||
|
|
9005fd31ed | ||
|
|
d2bded23c3 | ||
|
|
c0cbc37f85 | ||
|
|
9df61055e2 | ||
|
|
074136b1e8 | ||
|
|
8db5951287 | ||
|
|
97c7e941eb | ||
|
|
354f5c3281 |
@@ -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
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ""
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user