Compare commits

..
Author SHA1 Message Date
Dax Raad 09446605ec Snapshot release v0.0.0-202508022055 2025-08-02 16:55:53 -04:00
Dax Raad 1f9e8cc4a5 Snapshot release v0.0.0-202508022053 2025-08-02 16:55:35 -04:00
Dax Raad 09c4b71632 wip: plugins 2025-08-02 16:47:11 -04:00
Dax Raad c9bffc0f46 ignore: i am a senior engineer 2025-08-02 16:29:45 -04:00
26 changed files with 135 additions and 209 deletions
+1 -12
View File
@@ -39,15 +39,6 @@ jobs:
with:
bun-version: 1.2.19
- name: Cache ~/.bun
id: cache-bun
uses: actions/cache@v3
with:
path: ~/.bun
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-
- name: Install makepkg
run: |
sudo apt-get update
@@ -62,11 +53,9 @@ jobs:
git config --global user.email "opencode@sst.dev"
git config --global user.name "opencode"
- name: Install dependencies
run: bun install
- name: Publish
run: |
bun install
OPENCODE_VERSION=${{ inputs.version }} ./script/publish.ts
env:
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
-1
View File
@@ -36,4 +36,3 @@
| 2025-07-31 | 118,339 (+4,795) | 143,344 (+3,027) | 261,683 (+7,822) |
| 2025-08-01 | 123,539 (+5,200) | 146,680 (+3,336) | 270,219 (+8,536) |
| 2025-08-02 | 127,864 (+4,325) | 149,236 (+2,556) | 277,100 (+6,881) |
| 2025-08-03 | 131,397 (+3,533) | 150,451 (+1,215) | 281,848 (+4,748) |
+5 -6
View File
@@ -10,7 +10,7 @@
},
"packages/function": {
"name": "@opencode/function",
"version": "0.3.122",
"version": "0.0.1",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "22.0.0",
@@ -25,7 +25,7 @@
},
"packages/opencode": {
"name": "opencode",
"version": "0.3.122",
"version": "0.0.0",
"bin": {
"opencode": "./bin/opencode",
},
@@ -39,7 +39,6 @@
"@octokit/rest": "22.0.0",
"@openauthjs/openauth": "0.4.3",
"@opencode-ai/plugin": "workspace:*",
"@opencode-ai/sdk": "workspace:*",
"@standard-schema/spec": "1.0.0",
"@zip.js/zip.js": "2.7.62",
"ai": "catalog:",
@@ -78,7 +77,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
"version": "0.3.122",
"version": "0.0.0",
"devDependencies": {
"@hey-api/openapi-ts": "0.80.1",
"@opencode-ai/sdk": "workspace:*",
@@ -88,7 +87,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
"version": "0.3.122",
"version": "0.0.0",
"devDependencies": {
"@hey-api/openapi-ts": "0.80.1",
"@tsconfig/node22": "catalog:",
@@ -97,7 +96,7 @@
},
"packages/web": {
"name": "@opencode/web",
"version": "0.3.122",
"version": "0.0.1",
"dependencies": {
"@astrojs/cloudflare": "^12.5.4",
"@astrojs/markdown-remark": "6.3.1",
+1 -1
View File
@@ -5,7 +5,7 @@
"type": "module",
"packageManager": "bun@1.2.14",
"scripts": {
"dev": "bun run --conditions=development packages/opencode/src/index.ts",
"dev": "bun run packages/opencode/src/index.ts",
"typecheck": "bun run --filter='*' typecheck",
"stainless": "./scripts/stainless",
"postinstall": "./script/hooks"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode/function",
"version": "0.3.123",
"version": "0.0.0-202508022053",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+2 -3
View File
@@ -1,12 +1,12 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "0.3.123",
"version": "0.0.0-202508022053",
"name": "opencode",
"type": "module",
"private": true,
"scripts": {
"typecheck": "tsc --noEmit",
"dev": "bun run --conditions=development ./src/index.ts"
"dev": "bun run ./src/index.ts"
},
"bin": {
"opencode": "./bin/opencode"
@@ -37,7 +37,6 @@
"@octokit/rest": "22.0.0",
"@openauthjs/openauth": "0.4.3",
"@opencode-ai/plugin": "workspace:*",
"@opencode-ai/sdk": "workspace:*",
"@standard-schema/spec": "1.0.0",
"@zip.js/zip.js": "2.7.62",
"ai": "catalog:",
+1 -5
View File
@@ -1,6 +1,5 @@
import { Log } from "../util/log"
import path from "path"
import os from "os"
import { z } from "zod"
import { App } from "../app/app"
import { Filesystem } from "../util/filesystem"
@@ -404,10 +403,7 @@ export namespace Config {
if (lineIndex !== -1 && lines[lineIndex].trim().startsWith("//")) {
continue // Skip if line is commented
}
let filePath = match.replace(/^\{file:/, "").replace(/\}$/, "")
if (filePath.startsWith("~/")) {
filePath = path.join(os.homedir(), filePath.slice(2))
}
const filePath = match.replace(/^\{file:/, "").replace(/\}$/, "")
const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(configDir, filePath)
const fileContent = (await Bun.file(resolvedPath).text()).trim()
// escape newlines/quotes, strip outer quotes
+1 -1
View File
@@ -77,4 +77,4 @@ export namespace ModelsDev {
}
}
setInterval(() => ModelsDev.refresh(), 60 * 1000 * 60).unref()
setInterval(() => ModelsDev.refresh(), 60 * 1000).unref()
+45 -28
View File
@@ -735,9 +735,10 @@ export namespace Session {
args,
},
)
await processor.track(options.toolCallId)
const result = await item.execute(args, {
sessionID: input.sessionID,
abort: options.abortSignal!,
abort: abort.signal,
messageID: assistantMsg.id,
callID: options.toolCallId,
metadata: async (val) => {
@@ -779,10 +780,11 @@ export namespace Session {
}
for (const [key, item] of Object.entries(await MCP.tools())) {
if (enabledTools[key] === false) continue
if (mode.tools[key] === false) continue
const execute = item.execute
if (!execute) continue
item.execute = async (args, opts) => {
await processor.track(opts.toolCallId)
const result = await execute(args, opts)
const output = result.content
.filter((x: any) => x.type === "text")
@@ -918,11 +920,15 @@ export namespace Session {
}
function createProcessor(assistantMsg: MessageV2.Assistant, model: ModelsDev.Model) {
const toolcalls: Record<string, MessageV2.ToolPart> = {}
let snapshot: string | undefined
const toolCalls: Record<string, MessageV2.ToolPart> = {}
const snapshots: Record<string, string> = {}
return {
async track(toolCallID: string) {
const hash = await Snapshot.track()
if (hash) snapshots[toolCallID] = hash
},
partFromToolCall(toolCallID: string) {
return toolcalls[toolCallID]
return toolCalls[toolCallID]
},
async process(stream: StreamTextResult<Record<string, AITool>, never>) {
try {
@@ -938,7 +944,7 @@ export namespace Session {
case "tool-input-start":
const part = await updatePart({
id: toolcalls[value.id]?.id ?? Identifier.ascending("part"),
id: toolCalls[value.id]?.id ?? Identifier.ascending("part"),
messageID: assistantMsg.id,
sessionID: assistantMsg.sessionID,
type: "tool",
@@ -948,7 +954,7 @@ export namespace Session {
status: "pending",
},
})
toolcalls[value.id] = part as MessageV2.ToolPart
toolCalls[value.id] = part as MessageV2.ToolPart
break
case "tool-input-delta":
@@ -958,7 +964,7 @@ export namespace Session {
break
case "tool-call": {
const match = toolcalls[value.toolCallId]
const match = toolCalls[value.toolCallId]
if (match) {
const part = await updatePart({
...match,
@@ -970,12 +976,12 @@ export namespace Session {
},
},
})
toolcalls[value.toolCallId] = part as MessageV2.ToolPart
toolCalls[value.toolCallId] = part as MessageV2.ToolPart
}
break
}
case "tool-result": {
const match = toolcalls[value.toolCallId]
const match = toolCalls[value.toolCallId]
if (match && match.state.status === "running") {
await updatePart({
...match,
@@ -991,13 +997,27 @@ export namespace Session {
},
},
})
delete toolcalls[value.toolCallId]
delete toolCalls[value.toolCallId]
const snapshot = snapshots[value.toolCallId]
if (snapshot) {
const patch = await Snapshot.patch(snapshot)
if (patch.files.length) {
await updatePart({
id: Identifier.ascending("part"),
messageID: assistantMsg.id,
sessionID: assistantMsg.sessionID,
type: "patch",
hash: patch.hash,
files: patch.files,
})
}
}
}
break
}
case "tool-error": {
const match = toolcalls[value.toolCallId]
const match = toolCalls[value.toolCallId]
if (match && match.state.status === "running") {
await updatePart({
...match,
@@ -1011,7 +1031,19 @@ export namespace Session {
},
},
})
delete toolcalls[value.toolCallId]
delete toolCalls[value.toolCallId]
const snapshot = snapshots[value.toolCallId]
if (snapshot) {
const patch = await Snapshot.patch(snapshot)
await updatePart({
id: Identifier.ascending("part"),
messageID: assistantMsg.id,
sessionID: assistantMsg.sessionID,
type: "patch",
hash: patch.hash,
files: patch.files,
})
}
}
break
}
@@ -1026,7 +1058,6 @@ export namespace Session {
sessionID: assistantMsg.sessionID,
type: "step-start",
})
snapshot = await Snapshot.track()
break
case "finish-step":
@@ -1042,20 +1073,6 @@ export namespace Session {
cost: usage.cost,
})
await updateMessage(assistantMsg)
if (snapshot) {
const patch = await Snapshot.patch(snapshot)
if (patch.files.length) {
await updatePart({
id: Identifier.ascending("part"),
messageID: assistantMsg.id,
sessionID: assistantMsg.sessionID,
type: "patch",
hash: patch.hash,
files: patch.files,
})
}
snapshot = undefined
}
break
case "text-start":
+21 -16
View File
@@ -60,28 +60,33 @@ export namespace SystemPrompt {
export async function custom() {
const { cwd, root } = App.info().path
const config = await Config.get()
const paths = new Set<string>()
const found = []
for (const item of CUSTOM_FILES) {
const matches = await Filesystem.findUp(item, cwd, root)
matches.forEach((path) => paths.add(path))
found.push(...matches.map((x) => Bun.file(x).text()))
}
paths.add(path.join(Global.Path.config, "AGENTS.md"))
paths.add(path.join(os.homedir(), ".claude", "CLAUDE.md"))
if (config.instructions) {
for (const instruction of config.instructions) {
const matches = await Filesystem.globUp(instruction, cwd, root).catch(() => [])
matches.forEach((path) => paths.add(path))
}
}
const found = Array.from(paths).map((p) =>
Bun.file(p)
found.push(
Bun.file(path.join(Global.Path.config, "AGENTS.md"))
.text()
.catch(() => ""),
)
found.push(
Bun.file(path.join(os.homedir(), ".claude", "CLAUDE.md"))
.text()
.catch(() => ""),
)
if (config.instructions) {
for (const instruction of config.instructions) {
try {
const matches = await Filesystem.globUp(instruction, cwd, root)
found.push(...matches.map((x) => Bun.file(x).text()))
} catch {
continue // Skip invalid glob patterns
}
}
}
return Promise.all(found).then((result) => result.filter(Boolean))
}
+1 -1
View File
@@ -39,7 +39,7 @@ export namespace Snapshot {
log.info("initialized")
}
await $`git --git-dir ${git} add .`.quiet().cwd(app.path.cwd).nothrow()
const hash = await $`git --git-dir ${git} write-tree`.quiet().cwd(app.path.cwd).nothrow().text()
const hash = await $`git --git-dir ${git} write-tree`.quiet().cwd(app.path.cwd).text()
return hash.trim()
}
+11 -23
View File
@@ -1,6 +1,4 @@
import { z } from "zod"
import { exec } from "child_process"
import { text } from "stream/consumers"
import { Tool } from "./tool"
import DESCRIPTION from "./bash.txt"
import { App } from "../app/app"
@@ -77,13 +75,9 @@ export const BashTool = Tool.define("bash", {
if (["cd", "rm", "cp", "mv", "mkdir", "touch", "chmod", "chown"].includes(command[0])) {
for (const arg of command.slice(1)) {
if (arg.startsWith("-") || (command[0] === "chmod" && arg.startsWith("+"))) continue
const resolved = await $`realpath ${arg}`
.quiet()
.nothrow()
.text()
.then((x) => x.trim())
const resolved = await $`realpath ${arg}`.text().then((x) => x.trim())
log.info("resolved path", { arg, resolved })
if (resolved && !Filesystem.contains(app.path.cwd, resolved)) {
if (!Filesystem.contains(app.path.cwd, resolved)) {
throw new Error(
`This command references paths outside of ${app.path.cwd} so it is not allowed to be executed.`,
)
@@ -118,24 +112,18 @@ export const BashTool = Tool.define("bash", {
})
}
const process = exec(params.command, {
const process = Bun.spawn({
cmd: ["bash", "-c", params.command],
cwd: app.path.cwd,
signal: ctx.abort,
maxBuffer: MAX_OUTPUT_LENGTH,
timeout,
signal: ctx.abort,
timeout: timeout,
stdout: "pipe",
stderr: "pipe",
})
const stdoutPromise = text(process.stdout!)
const stderrPromise = text(process.stderr!)
await new Promise<void>((resolve) => {
process.on("close", () => {
resolve()
})
})
const stdout = await stdoutPromise
const stderr = await stderrPromise
await process.exited
const stdout = await new Response(process.stdout).text()
const stderr = await new Response(process.stderr).text()
return {
title: params.command,
-5
View File
@@ -2,11 +2,6 @@
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/bun/tsconfig.json",
"compilerOptions": {
"lib": [
"ESNext",
"DOM",
"DOM.Iterable"
],
"customConditions": [
"development"
]
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/plugin",
"version": "0.3.123",
"version": "0.0.0",
"type": "module",
"scripts": {
"typecheck": "tsc --noEmit"
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/sdk",
"version": "0.3.123",
"version": "0.0.0-202508022053",
"type": "module",
"scripts": {
"typecheck": "tsc --noEmit"
+9 -1
View File
@@ -4,11 +4,16 @@ const dir = new URL("..", import.meta.url).pathname
process.chdir(dir)
import { $ } from "bun"
import fs from "fs/promises"
import path from "path"
console.log("=== Generating JS SDK ===")
console.log()
import { createClient } from "@hey-api/openapi-ts"
await $`bun dev generate > ${dir}/openapi.json`.cwd(path.resolve(dir, "../../opencode"))
await fs.rm(path.join(dir, "src/gen"), { recursive: true, force: true })
await $`bun run ../../opencode/src/index.ts generate > openapi.json`
await createClient({
input: "./openapi.json",
@@ -32,3 +37,6 @@ await createClient({
],
})
await $`bun prettier --write src/gen`
await $`rm -rf dist`
await $`bun tsc`
-2
View File
@@ -6,8 +6,6 @@ process.chdir(dir)
import { $ } from "bun"
await import("./generate")
await $`rm -rf dist`
await $`bun tsc`
const snapshot = process.env["OPENCODE_SNAPSHOT"] === "true"
-4
View File
@@ -7,12 +7,8 @@
"declaration": true,
"moduleResolution": "bundler",
"lib": [
"es2022",
"dom",
"dom.iterable"
],
"customConditions": [
"development"
]
},
"include": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@opencode/web",
"type": "module",
"version": "0.3.123",
"version": "0.0.0-202508022053",
"scripts": {
"dev": "astro dev",
"dev:remote": "sst shell --stage=dev --target=Web astro dev",
-5
View File
@@ -1,5 +0,0 @@
User-agent: *
Allow: /
# Disallow shared content pages
Disallow: /s/
+7 -26
View File
@@ -230,20 +230,6 @@ You can also define agents using markdown files in `~/.config/opencode/agent/` o
You can disable providers that are loaded automatically through the `disabled_providers` option. This is useful when you want to prevent certain providers from being loaded even if their credentials are available.
The `disabled_providers` option accepts an array of provider IDs. When a provider is disabled:
- It won't be loaded even if environment variables are set
- It won't be loaded even if API keys are configured through `opencode auth login`
- The provider's models won't appear in the model selection list
```json title="opencode.json"
{
"$schema": "https://opencode.ai/config.json",
"disabled_providers": ["openai", "gemini"]
}
```
---
### Formatters
@@ -253,21 +239,16 @@ You can configure code formatters through the `formatter` option. See [Formatter
```json title="opencode.json"
{
"$schema": "https://opencode.ai/config.json",
"formatter": {
"prettier": {
"disabled": true
},
"custom-prettier": {
"command": ["npx", "prettier", "--write", "$FILE"],
"environment": {
"NODE_ENV": "development"
},
"extensions": [".js", ".ts", ".jsx", ".tsx"]
}
}
"disabled_providers": ["openai", "gemini"]
}
```
The `disabled_providers` option accepts an array of provider IDs. When a provider is disabled:
- It won't be loaded even if environment variables are set
- It won't be loaded even if API keys are configured through `opencode auth login`
- The provider's models won't appear in the model selection list
---
### Permissions
@@ -331,6 +331,7 @@ Or if you already have an API key, you can select **Manually enter API Key** and
1. Head over to the [Cerebras console](https://inference.cerebras.ai/), create an account, and generate an API key.
2. Run `opencode auth login` and select **Cerebras**.
```bash
@@ -588,7 +589,7 @@ To use Kimi K2 from Moonshot AI:
5. Configure Moonshot in your opencode config.
```json title="opencode.json" ""moonshot"" {5-15}
```json title="opencode.json" "\"moonshot\"" {5-15}
{
"$schema": "https://opencode.ai/config.json",
"provider": {
@@ -795,42 +796,6 @@ https://platform.openai.com/api-keys
---
### Zhipu AI
1. Head over to the [Zhipu API console](https://z.ai/manage-apikey/apikey-list), create an account, and click **Create a new API key**.
2. Run `opencode auth login` and select **Zhipu AI**.
```bash
$ opencode auth login
┌ Add credential
◆ Select provider
│ ● Zhipu AI
│ ...
```
3. Enter your Zhipu AI API key.
```bash
$ opencode auth login
┌ Add credential
◇ Select provider
│ Zhipu AI
◇ Enter your API key
│ _
```
4. Run the `/models` command to select a model like _GLM-4.5_.
---
## Troubleshooting
If you are having trouble with configuring a provider, check the following:
+1 -1
View File
@@ -71,7 +71,7 @@ const ogImage = `${config.socialCard}/opencode-share/${encodedTitle}.png?model=$
tag: "meta",
attrs: {
name: "robots",
content: "noindex, nofollow, noarchive, nosnippet",
content: "noindex",
}
},
{
+7 -26
View File
@@ -2,8 +2,6 @@
import { $ } from "bun"
console.log("=== publishing ===\n")
const snapshot = process.env["OPENCODE_SNAPSHOT"] === "true"
const version = snapshot
? `0.0.0-${new Date().toISOString().slice(0, 16).replace(/[-:T]/g, "")}`
@@ -12,45 +10,28 @@ if (!version) {
throw new Error("OPENCODE_VERSION is required")
}
process.env["OPENCODE_VERSION"] = version
console.log("version:", version)
const pkgjsons = await Array.fromAsync(
new Bun.Glob("**/package.json").scan({
absolute: true,
}),
).then((arr) => arr.filter((x) => !x.includes("node_modules") && !x.includes("dist")))
const tree = await $`git add . && git write-tree`.text().then((x) => x.trim())
for (const file of pkgjsons) {
for await (const file of new Bun.Glob("**/package.json").scan({
absolute: true,
})) {
let pkg = await Bun.file(file).text()
pkg = pkg.replaceAll(/"version": "[^"]+"/g, `"version": "${version}"`)
console.log("updated:", file)
await Bun.file(file).write(pkg)
}
console.log("\n=== opencode ===\n")
await import(`../packages/opencode/script/publish.ts`)
console.log("\n=== sdk ===\n")
// await import(`../packages/opencode/script/publish.ts`)
await import(`../packages/sdk/js/script/publish.ts`)
console.log("\n=== plugin ===\n")
await import(`../packages/plugin/script/publish.ts`)
// await import(`../packages/sdk/stainless/generate.ts`)
if (!snapshot) {
await $`git commit -am "release: v${version}"`
await $`git tag v${version}`
await $`git push origin HEAD --tags --no-verify`
await $`git push origin HEAD --tags`
}
if (snapshot) {
await $`git checkout -b snapshot-${version}`
await $`git commit --allow-empty -m "Snapshot release v${version}"`
await $`git tag v${version}`
await $`git push origin v${version} --no-verify`
await $`git checkout dev`
await $`git branch -D snapshot-${version}`
for (const file of pkgjsons) {
await $`git checkout ${tree} ${file}`
}
await $`git push origin v${version}`
await $`git reset --soft HEAD~1`
}
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "opencode",
"displayName": "opencode",
"description": "opencode for VS Code",
"version": "0.3.123",
"version": "0.0.0-202508022053",
"publisher": "sst-dev",
"repository": {
"type": "git",
+15
View File
@@ -0,0 +1,15 @@
import * as assert from 'assert';
// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
import * as vscode from 'vscode';
// import * as myExtension from '../../extension';
suite('Extension Test Suite', () => {
vscode.window.showInformationMessage('Start all tests.');
test('Sample test', () => {
assert.strictEqual(-1, [1, 2, 3].indexOf(5));
assert.strictEqual(-1, [1, 2, 3].indexOf(0));
});
});