mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-03 16:56:33 -04:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c9c9db1e8d | |||
| b11fe9fbc6 | |||
| 60f3d413de | |||
| 1df2d78b85 | |||
| 2286a872c1 | |||
| 8a83301e0d | |||
| 9bc40f00e3 | |||
| c3c440948a | |||
| aa10f8a7f6 | |||
| a2db58f125 | |||
| 574be9febf | |||
| 5b05ede748 | |||
| 4032426185 | |||
| 8d8045ff95 | |||
| b3c8bec019 | |||
| 25f43adaa0 | |||
| 4913ee6afd | |||
| c59ded82b3 | |||
| 40bdbf92a3 | |||
| ad76d7e57d | |||
| 863ae6fa7d | |||
| 8f230ad4b4 | |||
| c0f90eb564 | |||
| 50fb337270 | |||
| e08ec077b0 | |||
| 796245d146 |
@@ -36,10 +36,8 @@ jobs:
|
||||
|
||||
Issue number:
|
||||
${{ github.event.issue.number }}
|
||||
Issue body:
|
||||
${{ github.event.issue.body }}
|
||||
|
||||
Please search through existing issues (excluding #${{ github.event.issue.number }}) in this repository to find any potential duplicates of this new issue.
|
||||
Lookup this issue and search through existing issues (excluding #${{ github.event.issue.number }}) in this repository to find any potential duplicates of this new issue.
|
||||
Consider:
|
||||
1. Similar titles or descriptions
|
||||
2. Same error messages or symptoms
|
||||
|
||||
@@ -24,3 +24,6 @@ example, if the page title is "Models", avoid using a section title like "Add
|
||||
new models". This might be unavoidable in some cases, but try to avoid it.
|
||||
|
||||
Check out the /packages/web/src/content/docs/docs/index.mdx as an example.
|
||||
|
||||
For JS or TS code snippets remove trailing semicolons and any trailing commas
|
||||
that might not be needed.
|
||||
|
||||
@@ -52,3 +52,5 @@
|
||||
| 2025-08-16 | 198,118 (+4,510) | 184,558 (+2,766) | 382,676 (+7,276) |
|
||||
| 2025-08-17 | 201,299 (+3,181) | 186,269 (+1,711) | 387,568 (+4,892) |
|
||||
| 2025-08-18 | 204,559 (+3,260) | 187,399 (+1,130) | 391,958 (+4,390) |
|
||||
| 2025-08-19 | 209,814 (+5,255) | 189,668 (+2,269) | 399,482 (+7,524) |
|
||||
| 2025-08-20 | 214,497 (+4,683) | 191,481 (+1,813) | 405,978 (+6,496) |
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"dev": "vinxi dev --host 0.0.0.0",
|
||||
"build": "vinxi build",
|
||||
"start": "vinxi start",
|
||||
"version": "0.5.7"
|
||||
"version": "0.5.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ibm/plex": "6.4.1",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/cloud-core",
|
||||
"version": "0.5.7",
|
||||
"version": "0.5.11",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/cloud-function",
|
||||
"version": "0.5.7",
|
||||
"version": "0.5.11",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/cloud-web",
|
||||
"version": "0.5.7",
|
||||
"version": "0.5.11",
|
||||
"private": true,
|
||||
"description": "",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/function",
|
||||
"version": "0.5.7",
|
||||
"version": "0.5.11",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "0.5.7",
|
||||
"version": "0.5.11",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
|
||||
@@ -100,7 +100,7 @@ export namespace Agent {
|
||||
tools: {},
|
||||
builtIn: false,
|
||||
}
|
||||
const { model, prompt, tools, description, temperature, top_p, mode, permission, ...extra } = value
|
||||
const { name, model, prompt, tools, description, temperature, top_p, mode, permission, ...extra } = value
|
||||
item.options = {
|
||||
...item.options,
|
||||
...extra,
|
||||
@@ -120,6 +120,8 @@ export namespace Agent {
|
||||
if (temperature != undefined) item.temperature = temperature
|
||||
if (top_p != undefined) item.topP = top_p
|
||||
if (mode) item.mode = mode
|
||||
// just here for consistency & to prevent it from being added as an option
|
||||
if (name) item.name = name
|
||||
|
||||
if (permission ?? cfg.permission) {
|
||||
item.permission = mergeAgentPermissions(cfg.permission ?? {}, permission ?? {})
|
||||
|
||||
@@ -46,7 +46,10 @@ const AgentCreateCommand = cmd({
|
||||
const spinner = prompts.spinner()
|
||||
|
||||
spinner.start("Generating agent configuration...")
|
||||
const generated = await Agent.generate({ description: query })
|
||||
const generated = await Agent.generate({ description: query }).catch((error) => {
|
||||
spinner.stop(`LLM failed to generate agent: ${error.message}`, 1)
|
||||
throw new UI.CancelledError()
|
||||
})
|
||||
spinner.stop(`Agent ${generated.identifier} generated`)
|
||||
|
||||
const availableTools = [
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Auth } from "../../auth"
|
||||
import { cmd } from "./cmd"
|
||||
import * as prompts from "@clack/prompts"
|
||||
import open from "open"
|
||||
import { UI } from "../ui"
|
||||
import { ModelsDev } from "../../provider/models"
|
||||
import { map, pipe, sortBy, values } from "remeda"
|
||||
@@ -159,9 +158,6 @@ export const AuthLoginCommand = cmd({
|
||||
const authorize = await method.authorize()
|
||||
|
||||
if (authorize.url) {
|
||||
try {
|
||||
await open(authorize.url)
|
||||
} catch (e) {}
|
||||
prompts.log.info("Go to: " + authorize.url)
|
||||
}
|
||||
|
||||
|
||||
@@ -67,11 +67,17 @@ export const RunCommand = cmd({
|
||||
await bootstrap({ cwd: process.cwd() }, async () => {
|
||||
const session = await (async () => {
|
||||
if (args.continue) {
|
||||
const list = Session.list()
|
||||
const first = await list.next()
|
||||
await list.return()
|
||||
if (first.done) return
|
||||
return first.value
|
||||
const it = Session.list()
|
||||
try {
|
||||
for await (const s of it) {
|
||||
if (s.parentID === undefined) {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return
|
||||
} finally {
|
||||
await it.return()
|
||||
}
|
||||
}
|
||||
|
||||
if (args.session) return Session.get(args.session)
|
||||
|
||||
@@ -82,11 +82,17 @@ export const TuiCommand = cmd({
|
||||
const result = await bootstrap({ cwd }, async (app) => {
|
||||
const sessionID = await (async () => {
|
||||
if (args.continue) {
|
||||
const list = Session.list()
|
||||
const first = await list.next()
|
||||
await list.return()
|
||||
if (first.done) return
|
||||
return first.value.id
|
||||
const it = Session.list()
|
||||
try {
|
||||
for await (const s of it) {
|
||||
if (s.parentID === undefined) {
|
||||
return s.id
|
||||
}
|
||||
}
|
||||
return
|
||||
} finally {
|
||||
await it.return()
|
||||
}
|
||||
}
|
||||
if (args.session) {
|
||||
return args.session
|
||||
|
||||
@@ -45,7 +45,7 @@ export const UpgradeCommand = {
|
||||
spinner.start("Upgrading...")
|
||||
const err = await Installation.upgrade(method, target).catch((err) => err)
|
||||
if (err) {
|
||||
spinner.stop("Upgrade failed")
|
||||
spinner.stop("Upgrade failed", 1)
|
||||
if (err instanceof Installation.UpgradeFailedError) prompts.log.error(err.data.stderr)
|
||||
else if (err instanceof Error) prompts.log.error(err.message)
|
||||
prompts.outro("Done")
|
||||
|
||||
@@ -64,7 +64,7 @@ export namespace Config {
|
||||
if (agentFolderPath.includes("/")) {
|
||||
const relativePath = agentFolderPath.replace(".md", "")
|
||||
const pathParts = relativePath.split("/")
|
||||
agentName = pathParts.slice(0, -1).join("/").toUpperCase() + "/" + pathParts[pathParts.length - 1].toUpperCase()
|
||||
agentName = pathParts.slice(0, -1).join("/") + "/" + pathParts[pathParts.length - 1]
|
||||
}
|
||||
|
||||
const config = {
|
||||
|
||||
@@ -28,7 +28,7 @@ await Promise.all([
|
||||
fs.mkdir(Global.Path.bin, { recursive: true }),
|
||||
])
|
||||
|
||||
const CACHE_VERSION = "8"
|
||||
const CACHE_VERSION = "9"
|
||||
|
||||
const version = await Bun.file(path.join(Global.Path.cache, "version"))
|
||||
.text()
|
||||
|
||||
@@ -25,7 +25,7 @@ export namespace Plugin {
|
||||
}
|
||||
const plugins = [...(config.plugin ?? [])]
|
||||
if (!Flag.OPENCODE_DISABLE_DEFAULT_PLUGINS) {
|
||||
plugins.push("opencode-copilot-auth")
|
||||
plugins.push("opencode-copilot-auth@0.0.2")
|
||||
plugins.push("opencode-anthropic-auth@0.0.2")
|
||||
}
|
||||
for (let plugin of plugins) {
|
||||
|
||||
@@ -36,6 +36,12 @@ export namespace Provider {
|
||||
},
|
||||
}
|
||||
},
|
||||
async opencode() {
|
||||
return {
|
||||
autoload: true,
|
||||
options: {},
|
||||
}
|
||||
},
|
||||
openai: async () => {
|
||||
return {
|
||||
autoload: false,
|
||||
@@ -276,6 +282,15 @@ export namespace Provider {
|
||||
}
|
||||
|
||||
for (const [providerID, provider] of Object.entries(providers)) {
|
||||
// Filter out blacklisted models
|
||||
const filteredModels = Object.fromEntries(
|
||||
Object.entries(provider.info.models).filter(
|
||||
([modelID]) =>
|
||||
modelID !== "gpt-5-chat-latest" && !(providerID === "openrouter" && modelID === "openai/gpt-5-chat"),
|
||||
),
|
||||
)
|
||||
provider.info.models = filteredModels
|
||||
|
||||
if (Object.keys(provider.info.models).length === 0) {
|
||||
delete providers[providerID]
|
||||
continue
|
||||
|
||||
@@ -726,6 +726,18 @@ export namespace Session {
|
||||
synthetic: true,
|
||||
})
|
||||
}
|
||||
|
||||
const lastAssistantMsg = msgs.filter((x) => x.info.role === "assistant").at(-1)?.info as MessageV2.Assistant
|
||||
if (lastAssistantMsg?.mode === "plan" && agent.name === "build") {
|
||||
msgs.at(-1)?.parts.push({
|
||||
id: Identifier.ascending("part"),
|
||||
messageID: userMsg.id,
|
||||
sessionID: input.sessionID,
|
||||
type: "text",
|
||||
text: "You are now in build mode and are permitted to make edits",
|
||||
synthetic: true,
|
||||
})
|
||||
}
|
||||
let system = SystemPrompt.header(input.providerID)
|
||||
system.push(
|
||||
...(() => {
|
||||
@@ -945,6 +957,13 @@ export namespace Session {
|
||||
toolName: "invalid",
|
||||
}
|
||||
},
|
||||
headers:
|
||||
input.providerID === "opencode"
|
||||
? {
|
||||
"x-opencode-session": input.sessionID,
|
||||
"x-opencode-request": userMsg.id,
|
||||
}
|
||||
: undefined,
|
||||
maxRetries: 3,
|
||||
activeTools: Object.keys(tools).filter((x) => x !== "invalid"),
|
||||
maxOutputTokens: outputLimit,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "0.5.7",
|
||||
"version": "0.5.11",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "0.5.7",
|
||||
"version": "0.5.11",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit"
|
||||
|
||||
@@ -207,6 +207,9 @@ func New(
|
||||
|
||||
func (a *App) Keybind(commandName commands.CommandName) string {
|
||||
command := a.Commands[commandName]
|
||||
if len(command.Keybindings) == 0 {
|
||||
return ""
|
||||
}
|
||||
kb := command.Keybindings[0]
|
||||
key := kb.Key
|
||||
if kb.RequiresLeader {
|
||||
|
||||
@@ -382,11 +382,9 @@ func (m *editorComponent) Content() string {
|
||||
status = "waiting for permission"
|
||||
}
|
||||
if m.interruptKeyInDebounce && m.app.CurrentPermission.ID == "" {
|
||||
bright := t.Accent()
|
||||
if status == "waiting for permission" {
|
||||
bright = t.Warning()
|
||||
}
|
||||
hint = util.Shimmer(status, t.Background(), t.TextMuted(), bright) + m.spinner.View() + muted(
|
||||
hint = muted(
|
||||
status,
|
||||
) + m.spinner.View() + muted(
|
||||
" ",
|
||||
) + base(
|
||||
keyText+" again",
|
||||
@@ -394,11 +392,7 @@ func (m *editorComponent) Content() string {
|
||||
" interrupt",
|
||||
)
|
||||
} else {
|
||||
bright := t.Accent()
|
||||
if status == "waiting for permission" {
|
||||
bright = t.Warning()
|
||||
}
|
||||
hint = util.Shimmer(status, t.Background(), t.TextMuted(), bright) + m.spinner.View()
|
||||
hint = muted(status) + m.spinner.View()
|
||||
if m.app.CurrentPermission.ID == "" {
|
||||
hint += muted(" ") + base(keyText) + muted(" interrupt")
|
||||
}
|
||||
|
||||
@@ -213,6 +213,7 @@ func renderText(
|
||||
extra string,
|
||||
isThinking bool,
|
||||
isQueued bool,
|
||||
shimmer bool,
|
||||
fileParts []opencode.FilePart,
|
||||
agentParts []opencode.AgentPart,
|
||||
toolCalls ...opencode.ToolPart,
|
||||
@@ -234,7 +235,12 @@ func renderText(
|
||||
}
|
||||
content = util.ToMarkdown(text, width, backgroundColor)
|
||||
if isThinking {
|
||||
label := util.Shimmer("Thinking...", backgroundColor, t.TextMuted(), t.Accent())
|
||||
var label string
|
||||
if shimmer {
|
||||
label = util.Shimmer("Thinking...", backgroundColor, t.TextMuted(), t.Accent())
|
||||
} else {
|
||||
label = styles.NewStyle().Background(backgroundColor).Foreground(t.TextMuted()).Render("Thinking...")
|
||||
}
|
||||
label = styles.NewStyle().Background(backgroundColor).Width(width - 6).Render(label)
|
||||
content = label + "\n\n" + content
|
||||
} else if strings.TrimSpace(text) == "Generating..." {
|
||||
@@ -665,10 +671,22 @@ func renderToolDetails(
|
||||
body = strings.Join(steps, "\n")
|
||||
|
||||
body += "\n\n"
|
||||
body += baseStyle(app.Keybind(commands.SessionChildCycleCommand)) +
|
||||
mutedStyle(", ") +
|
||||
baseStyle(app.Keybind(commands.SessionChildCycleReverseCommand)) +
|
||||
mutedStyle(" navigate child sessions")
|
||||
|
||||
// Build navigation hint with proper spacing
|
||||
cycleKeybind := app.Keybind(commands.SessionChildCycleCommand)
|
||||
cycleReverseKeybind := app.Keybind(commands.SessionChildCycleReverseCommand)
|
||||
|
||||
var navParts []string
|
||||
if cycleKeybind != "" {
|
||||
navParts = append(navParts, baseStyle(cycleKeybind))
|
||||
}
|
||||
if cycleReverseKeybind != "" {
|
||||
navParts = append(navParts, baseStyle(cycleReverseKeybind))
|
||||
}
|
||||
|
||||
if len(navParts) > 0 {
|
||||
body += strings.Join(navParts, mutedStyle(", ")) + mutedStyle(" navigate child sessions")
|
||||
}
|
||||
}
|
||||
body = defaultStyle(body)
|
||||
default:
|
||||
@@ -903,7 +921,9 @@ func renderArgs(args *map[string]any, titleKey string) string {
|
||||
continue
|
||||
}
|
||||
if key == "filePath" || key == "path" {
|
||||
value = util.Relative(value.(string))
|
||||
if strValue, ok := value.(string); ok {
|
||||
value = util.Relative(strValue)
|
||||
}
|
||||
}
|
||||
if key == titleKey {
|
||||
title = fmt.Sprintf("%s", value)
|
||||
|
||||
@@ -336,6 +336,25 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
||||
|
||||
width := m.width // always use full width
|
||||
|
||||
// Find the last streaming ReasoningPart to only shimmer that one
|
||||
lastStreamingReasoningID := ""
|
||||
if m.showThinkingBlocks {
|
||||
for mi := len(m.app.Messages) - 1; mi >= 0 && lastStreamingReasoningID == ""; mi-- {
|
||||
if _, ok := m.app.Messages[mi].Info.(opencode.AssistantMessage); !ok {
|
||||
continue
|
||||
}
|
||||
parts := m.app.Messages[mi].Parts
|
||||
for pi := len(parts) - 1; pi >= 0; pi-- {
|
||||
if rp, ok := parts[pi].(opencode.ReasoningPart); ok {
|
||||
if strings.TrimSpace(rp.Text) != "" && rp.Time.End == 0 {
|
||||
lastStreamingReasoningID = rp.ID
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reverted := false
|
||||
revertedMessageCount := 0
|
||||
revertedToolCount := 0
|
||||
@@ -437,6 +456,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
||||
files,
|
||||
false,
|
||||
isQueued,
|
||||
false,
|
||||
fileParts,
|
||||
agentParts,
|
||||
)
|
||||
@@ -513,6 +533,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
[]opencode.FilePart{},
|
||||
[]opencode.AgentPart{},
|
||||
toolCallParts...,
|
||||
@@ -530,6 +551,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
[]opencode.FilePart{},
|
||||
[]opencode.AgentPart{},
|
||||
toolCallParts...,
|
||||
@@ -600,6 +622,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
||||
}
|
||||
if part.Text != "" {
|
||||
text := part.Text
|
||||
shimmer := part.Time.End == 0 && part.ID == lastStreamingReasoningID
|
||||
content = renderText(
|
||||
m.app,
|
||||
message.Info,
|
||||
@@ -610,6 +633,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
||||
"",
|
||||
true,
|
||||
false,
|
||||
shimmer,
|
||||
[]opencode.FilePart{},
|
||||
[]opencode.AgentPart{},
|
||||
)
|
||||
@@ -644,6 +668,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
[]opencode.FilePart{},
|
||||
[]opencode.AgentPart{},
|
||||
)
|
||||
@@ -746,16 +771,18 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
||||
} else {
|
||||
for _, part := range response.Parts {
|
||||
if part.CallID == m.app.CurrentPermission.CallID {
|
||||
content := renderToolDetails(
|
||||
m.app,
|
||||
part.AsUnion().(opencode.ToolPart),
|
||||
m.app.CurrentPermission,
|
||||
width,
|
||||
)
|
||||
if content != "" {
|
||||
partCount++
|
||||
lineCount += lipgloss.Height(content) + 1
|
||||
blocks = append(blocks, content)
|
||||
if toolPart, ok := part.AsUnion().(opencode.ToolPart); ok {
|
||||
content := renderToolDetails(
|
||||
m.app,
|
||||
toolPart,
|
||||
m.app.CurrentPermission,
|
||||
width,
|
||||
)
|
||||
if content != "" {
|
||||
partCount++
|
||||
lineCount += lipgloss.Height(content) + 1
|
||||
blocks = append(blocks, content)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,31 @@ package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
opencode "github.com/sst/opencode-sdk-go"
|
||||
)
|
||||
|
||||
func sanitizeValue(val any) any {
|
||||
if val == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err, ok := val.(error); ok {
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(val)
|
||||
if v.Kind() == reflect.Interface && !v.IsNil() {
|
||||
return fmt.Sprintf("%T", val)
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
type APILogHandler struct {
|
||||
client *opencode.Client
|
||||
service string
|
||||
@@ -67,21 +86,13 @@ func (h *APILogHandler) Handle(ctx context.Context, r slog.Record) error {
|
||||
h.mu.Lock()
|
||||
for _, attr := range h.attrs {
|
||||
val := attr.Value.Any()
|
||||
if err, ok := val.(error); ok {
|
||||
extra[attr.Key] = err.Error()
|
||||
} else {
|
||||
extra[attr.Key] = val
|
||||
}
|
||||
extra[attr.Key] = sanitizeValue(val)
|
||||
}
|
||||
h.mu.Unlock()
|
||||
|
||||
r.Attrs(func(attr slog.Attr) bool {
|
||||
val := attr.Value.Any()
|
||||
if err, ok := val.(error); ok {
|
||||
extra[attr.Key] = err.Error()
|
||||
} else {
|
||||
extra[attr.Key] = val
|
||||
}
|
||||
extra[attr.Key] = sanitizeValue(val)
|
||||
return true
|
||||
})
|
||||
|
||||
|
||||
@@ -9,8 +9,6 @@ import { rehypeHeadingIds } from "@astrojs/markdown-remark"
|
||||
import rehypeAutolinkHeadings from "rehype-autolink-headings"
|
||||
import { spawnSync } from "child_process"
|
||||
|
||||
const github = "https://github.com/sst/opencode"
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
site: config.url,
|
||||
@@ -49,7 +47,7 @@ export default defineConfig({
|
||||
},
|
||||
],
|
||||
editLink: {
|
||||
baseUrl: `${github}/edit/dev/packages/web/`,
|
||||
baseUrl: `${config.github}/edit/dev/packages/web/`,
|
||||
},
|
||||
markdown: {
|
||||
headingLinks: false,
|
||||
@@ -79,7 +77,6 @@ export default defineConfig({
|
||||
"docs/agents",
|
||||
"docs/models",
|
||||
"docs/themes",
|
||||
"docs/plugins",
|
||||
"docs/keybinds",
|
||||
"docs/formatters",
|
||||
"docs/permissions",
|
||||
@@ -87,6 +84,11 @@ export default defineConfig({
|
||||
"docs/mcp-servers",
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
label: "Develop",
|
||||
items: ["docs/sdk", "docs/server", "docs/plugins"],
|
||||
},
|
||||
],
|
||||
components: {
|
||||
Hero: "./src/components/Hero.astro",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode/web",
|
||||
"type": "module",
|
||||
"version": "0.5.7",
|
||||
"version": "0.5.11",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"dev:remote": "sst shell --stage=dev --target=Web astro dev",
|
||||
|
||||
@@ -176,7 +176,7 @@ opencode run Explain the use of context in Go
|
||||
|
||||
### serve
|
||||
|
||||
Start a headless opencode server for API access.
|
||||
Start a headless opencode server for API access. Check out the [server docs](/docs/server) for the full HTTP interface.
|
||||
|
||||
```bash
|
||||
opencode serve
|
||||
|
||||
@@ -20,7 +20,6 @@ opencode has a list of keybinds that you can customize through the opencode conf
|
||||
"session_export": "<leader>x",
|
||||
"session_new": "<leader>n",
|
||||
"session_list": "<leader>l",
|
||||
"session_navigation": "<leader>g",
|
||||
"session_share": "<leader>s",
|
||||
"session_unshare": "none",
|
||||
"session_interrupt": "esc",
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
---
|
||||
title: SDK
|
||||
description: JS SDK for the opencode server.
|
||||
---
|
||||
|
||||
import config from "../../../../config.mjs"
|
||||
export const typesUrl = `${config.github}/blob/dev/packages/sdk/js/src/gen/types.gen.ts`
|
||||
|
||||
The opencode [JS/TS SDK](https://www.npmjs.com/package/@opencode-ai/sdk) provides a type-safe client for interacting with the opencode server. You can use it to build custom integrations and control opencode programmatically.
|
||||
|
||||
---
|
||||
|
||||
## Install
|
||||
|
||||
Install the SDK from npm:
|
||||
|
||||
```bash
|
||||
npm install @opencode-ai/sdk
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Create client
|
||||
|
||||
Create a client instance to connect to your opencode server:
|
||||
|
||||
```javascript
|
||||
import { createOpencodeClient } from "@opencode-ai/sdk"
|
||||
|
||||
const client = createOpencodeClient({
|
||||
baseUrl: "http://localhost:4096",
|
||||
})
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Type | Description | Default |
|
||||
| --------- | ---------- | --------------------------- | ----------------------- |
|
||||
| `baseUrl` | `string` | URL of the opencode server | `http://localhost:4096` |
|
||||
| `fetch` | `function` | Custom fetch implementation | `globalThis.fetch` |
|
||||
|
||||
---
|
||||
|
||||
## Start server
|
||||
|
||||
You can also programmatically start an opencode server:
|
||||
|
||||
```javascript
|
||||
import { createOpencodeServer } from "@opencode-ai/sdk"
|
||||
|
||||
const server = await createOpencodeServer({
|
||||
host: "127.0.0.1",
|
||||
port: 4096,
|
||||
})
|
||||
|
||||
console.log(`Server running at ${server.url}`)
|
||||
|
||||
server.close()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Types
|
||||
|
||||
The SDK includes TypeScript definitions for all API types. Import them directly:
|
||||
|
||||
```typescript
|
||||
import type { Session, Message, Part } from "@opencode-ai/sdk"
|
||||
```
|
||||
|
||||
All types are generated from the server's OpenAPI specification and available in the <a href={typesUrl}>types file</a>.
|
||||
|
||||
---
|
||||
|
||||
## APIs
|
||||
|
||||
The SDK exposes all server APIs through a type-safe client interface.
|
||||
|
||||
---
|
||||
|
||||
### App
|
||||
|
||||
| Method | Description | Response |
|
||||
| ------------ | ------------------ | --------------------------------------- |
|
||||
| `app.get()` | Get app info | <a href={typesUrl}><code>App</code></a> |
|
||||
| `app.init()` | Initialize the app | `boolean` |
|
||||
|
||||
---
|
||||
|
||||
#### Examples
|
||||
|
||||
```javascript
|
||||
const app = await client.app.get()
|
||||
await client.app.init()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Config
|
||||
|
||||
| Method | Description | Response |
|
||||
| -------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
||||
| `config.get()` | Get config info | <a href={typesUrl}><code>Config</code></a> |
|
||||
| `config.providers()` | List providers and default models | `{ providers: `<a href={typesUrl}><code>Provider[]</code></a>`, default: { [key: string]: string } }` |
|
||||
|
||||
---
|
||||
|
||||
#### Examples
|
||||
|
||||
```javascript
|
||||
const config = await client.config.get()
|
||||
const { providers, default: defaults } = await client.config.providers()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Sessions
|
||||
|
||||
| Method | Description | Notes |
|
||||
| ------------------------------------------------------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| `session.list()` | List sessions | Returns <a href={typesUrl}><code>Session[]</code></a> |
|
||||
| `session.get({ id })` | Get session | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||
| `session.children({ id })` | List child sessions | Returns <a href={typesUrl}><code>Session[]</code></a> |
|
||||
| `session.create({ parentID?, title? })` | Create session | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||
| `session.delete({ id })` | Delete session | Returns `boolean` |
|
||||
| `session.update({ id, title? })` | Update session properties | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||
| `session.init({ id, messageID, providerID, modelID })` | Analyze app and create `AGENTS.md` | Returns `boolean` |
|
||||
| `session.abort({ id })` | Abort a running session | Returns `boolean` |
|
||||
| `session.share({ id })` | Share session | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||
| `session.unshare({ id })` | Unshare session | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||
| `session.summarize({ id, providerID, modelID })` | Summarize session | Returns `boolean` |
|
||||
| `session.messages({ id })` | List messages in a session | Returns `{ info: `<a href={typesUrl}><code>Message</code></a>`, parts: `<a href={typesUrl}><code>Part[]</code></a>`}[]` |
|
||||
| `session.message({ id, messageID })` | Get message details | Returns `{ info: `<a href={typesUrl}><code>Message</code></a>`, parts: `<a href={typesUrl}><code>Part[]</code></a>`}` |
|
||||
| `session.chat({ id, ...chatInput })` | Send chat message | Returns <a href={typesUrl}><code>Message</code></a> |
|
||||
| `session.shell({ id, agent, command })` | Run a shell command | Returns <a href={typesUrl}><code>Message</code></a> |
|
||||
| `session.revert({ id, messageID, partID? })` | Revert a message | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||
| `session.unrevert({ id })` | Restore reverted messages | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||
| `session.permissions.respond({ id, permissionID, response })` | Respond to a permission request | Returns `boolean` |
|
||||
|
||||
---
|
||||
|
||||
#### Examples
|
||||
|
||||
```javascript
|
||||
// Create and manage sessions
|
||||
const session = await client.session.create({ title: "My session" })
|
||||
const sessions = await client.session.list()
|
||||
|
||||
// Send messages
|
||||
const message = await client.session.chat({
|
||||
id: session.id,
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-3-5-sonnet-20241022",
|
||||
parts: [{ type: "text", text: "Hello!" }],
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Files
|
||||
|
||||
| Method | Description | Response |
|
||||
| ------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------- |
|
||||
| `find.text({ pattern })` | Search for text in files | Array of match objects with `path`, `lines`, `line_number`, `absolute_offset`, `submatches` |
|
||||
| `find.files({ query })` | Find files by name | `string[]` (file paths) |
|
||||
| `find.symbols({ query })` | Find workspace symbols | <a href={typesUrl}><code>Symbol[]</code></a> |
|
||||
| `file.read({ path })` | Read a file | `{ type: "raw" \| "patch", content: string }` |
|
||||
| `file.status()` | Get status for tracked files | <a href={typesUrl}><code>File[]</code></a> |
|
||||
|
||||
---
|
||||
|
||||
#### Examples
|
||||
|
||||
```javascript
|
||||
// Search and read files
|
||||
const textResults = await client.find.text({ pattern: "function.*opencode" })
|
||||
const files = await client.find.files({ query: "*.ts" })
|
||||
const content = await client.file.read({ path: "src/index.ts" })
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Logging
|
||||
|
||||
| Method | Description | Response |
|
||||
| ------------------------------------------------ | --------------- | --------- |
|
||||
| `log.write({ service, level, message, extra? })` | Write log entry | `boolean` |
|
||||
|
||||
---
|
||||
|
||||
#### Examples
|
||||
|
||||
```javascript
|
||||
await client.log.write({
|
||||
service: "my-app",
|
||||
level: "info",
|
||||
message: "Operation completed",
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Agents
|
||||
|
||||
| Method | Description | Response |
|
||||
| -------------- | ------------------------- | ------------------------------------------- |
|
||||
| `agent.list()` | List all available agents | <a href={typesUrl}><code>Agent[]</code></a> |
|
||||
|
||||
---
|
||||
|
||||
#### Examples
|
||||
|
||||
```javascript
|
||||
const agents = await client.agent.list()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### TUI
|
||||
|
||||
| Method | Description | Response |
|
||||
| --------------------------------------------- | --------------------------------- | ---------------------- |
|
||||
| `tui.appendPrompt({ text })` | Append text to the prompt | `boolean` |
|
||||
| `tui.openHelp()` | Open the help dialog | `boolean` |
|
||||
| `tui.openSessions()` | Open the session selector | `boolean` |
|
||||
| `tui.openThemes()` | Open the theme selector | `boolean` |
|
||||
| `tui.openModels()` | Open the model selector | `boolean` |
|
||||
| `tui.submitPrompt()` | Submit the current prompt | `boolean` |
|
||||
| `tui.clearPrompt()` | Clear the prompt | `boolean` |
|
||||
| `tui.executeCommand({ command })` | Execute a command | `boolean` |
|
||||
| `tui.showToast({ title?, message, variant })` | Show toast notification | `boolean` |
|
||||
| `tui.control.next()` | Wait for the next control request | Control request object |
|
||||
| `tui.control.response({ body })` | Respond to a control request | `boolean` |
|
||||
|
||||
---
|
||||
|
||||
#### Examples
|
||||
|
||||
```javascript
|
||||
// Control TUI interface
|
||||
await client.tui.appendPrompt({ text: "Add this to prompt" })
|
||||
await client.tui.showToast({
|
||||
message: "Task completed",
|
||||
variant: "success",
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Auth
|
||||
|
||||
| Method | Description | Response |
|
||||
| ------------------------------- | ------------------------------ | --------- |
|
||||
| `auth.set({ id, ...authData })` | Set authentication credentials | `boolean` |
|
||||
|
||||
---
|
||||
|
||||
#### Examples
|
||||
|
||||
```javascript
|
||||
await client.auth.set({
|
||||
id: "anthropic",
|
||||
type: "api",
|
||||
key: "your-api-key",
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Events
|
||||
|
||||
| Method | Description | Response |
|
||||
| ------------------- | ------------------------- | ------------------------- |
|
||||
| `event.subscribe()` | Server-sent events stream | Server-sent events stream |
|
||||
|
||||
---
|
||||
|
||||
#### Examples
|
||||
|
||||
```javascript
|
||||
// Listen to real-time events
|
||||
const eventStream = await client.event.subscribe()
|
||||
for await (const event of eventStream) {
|
||||
console.log("Event:", event.type, event.properties)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error handling
|
||||
|
||||
The SDK throws typed errors that you can catch and handle:
|
||||
|
||||
```typescript
|
||||
try {
|
||||
const session = await client.session.get({ id: "invalid-id" })
|
||||
} catch (error) {
|
||||
console.error("Failed to get session:", error.message)
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,155 @@
|
||||
---
|
||||
title: Server
|
||||
description: Interact with opencode server over HTTP.
|
||||
---
|
||||
|
||||
import config from "../../../../config.mjs"
|
||||
export const typesUrl = `${config.github}/blob/dev/packages/sdk/js/src/gen/types.gen.ts`
|
||||
|
||||
The `opencode serve` command runs a headless HTTP server that exposes an API that an opencode client, like the TUI, uses. You can use this to create your own opencode client and even control a currently running TUI.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
opencode serve [--port <number>] [--hostname <string>]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Flag | Short | Description | Default |
|
||||
| ------------ | ----- | --------------------- | ----------- |
|
||||
| `--port` | `-p` | Port to listen on | `4096` |
|
||||
| `--hostname` | `-h` | Hostname to listen on | `127.0.0.1` |
|
||||
|
||||
---
|
||||
|
||||
## Docs
|
||||
|
||||
The server publishes an OpenAPI 3.1 spec that can be viewed at:
|
||||
|
||||
```
|
||||
http://<hostname>:<port>/doc
|
||||
```
|
||||
|
||||
For example, `http://localhost:4096/doc`. Use the spec to generate clients or inspect request and response types. Or view it in a Swagger explorer.
|
||||
|
||||
---
|
||||
|
||||
## APIs
|
||||
|
||||
The opencode server exposes the following APIs.
|
||||
|
||||
---
|
||||
|
||||
### App
|
||||
|
||||
| Method | Path | Description | Response |
|
||||
| ------ | ----------- | ------------------ | --------------------------------------- |
|
||||
| `GET` | `/app` | Get app info | <a href={typesUrl}><code>App</code></a> |
|
||||
| `POST` | `/app/init` | Initialize the app | `boolean` |
|
||||
|
||||
---
|
||||
|
||||
### Config
|
||||
|
||||
| Method | Path | Description | Response |
|
||||
| ------ | ------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
||||
| `GET` | `/config` | Get config info | <a href={typesUrl}><code>Config</code></a> |
|
||||
| `GET` | `/config/providers` | List providers and default models | `{ providers: `<a href={typesUrl}>Provider[]</a>`, default: { [key: string]: string } }` |
|
||||
|
||||
---
|
||||
|
||||
### Sessions
|
||||
|
||||
| Method | Path | Description | Notes |
|
||||
| -------- | ---------------------------------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `GET` | `/session` | List sessions | Returns <a href={typesUrl}><code>Session[]</code></a> |
|
||||
| `GET` | `/session/:id` | Get session | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||
| `GET` | `/session/:id/children` | List child sessions | Returns <a href={typesUrl}><code>Session[]</code></a> |
|
||||
| `POST` | `/session` | Create session | body: `{ parentID?, title? }`, returns <a href={typesUrl}><code>Session</code></a> |
|
||||
| `DELETE` | `/session/:id` | Delete session | |
|
||||
| `PATCH` | `/session/:id` | Update session properties | body: `{ title? }`, returns <a href={typesUrl}><code>Session</code></a> |
|
||||
| `POST` | `/session/:id/init` | Analyze app and create `AGENTS.md` | body: `{ messageID, providerID, modelID }` |
|
||||
| `POST` | `/session/:id/abort` | Abort a running session | |
|
||||
| `POST` | `/session/:id/share` | Share session | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||
| `DELETE` | `/session/:id/share` | Unshare session | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||
| `POST` | `/session/:id/summarize` | Summarize session | |
|
||||
| `GET` | `/session/:id/message` | List messages in a session | Returns `{ info: `<a href={typesUrl}>Message</a>`, parts: `<a href={typesUrl}>Part[]</a>`}[]` |
|
||||
| `GET` | `/session/:id/message/:messageID` | Get message details | Returns `{ info: `<a href={typesUrl}>Message</a>`, parts: `<a href={typesUrl}>Part[]</a>`}` |
|
||||
| `POST` | `/session/:id/message` | Send chat message | body matches [`ChatInput`](https://github.com/sst/opencode/blob/main/packages/opencode/src/session/index.ts#L358), returns <a href={typesUrl}><code>Message</code></a> |
|
||||
| `POST` | `/session/:id/shell` | Run a shell command | body matches [`CommandInput`](https://github.com/sst/opencode/blob/main/packages/opencode/src/session/index.ts#L1007), returns <a href={typesUrl}><code>Message</code></a> |
|
||||
| `POST` | `/session/:id/revert` | Revert a message | body: `{ messageID }` |
|
||||
| `POST` | `/session/:id/unrevert` | Restore reverted messages | |
|
||||
| `POST` | `/session/:id/permissions/:permissionID` | Respond to a permission request | body: `{ response }` |
|
||||
|
||||
---
|
||||
|
||||
### Files
|
||||
|
||||
| Method | Path | Description | Response |
|
||||
| ------ | ------------------------ | ---------------------------- | ------------------------------------------------------------------------------------------- |
|
||||
| `GET` | `/find?pattern=<pat>` | Search for text in files | Array of match objects with `path`, `lines`, `line_number`, `absolute_offset`, `submatches` |
|
||||
| `GET` | `/find/file?query=<q>` | Find files by name | `string[]` (file paths) |
|
||||
| `GET` | `/find/symbol?query=<q>` | Find workspace symbols | <a href={typesUrl}><code>Symbol[]</code></a> |
|
||||
| `GET` | `/file?path=<path>` | Read a file | `{ type: "raw" \| "patch", content: string }` |
|
||||
| `GET` | `/file/status` | Get status for tracked files | <a href={typesUrl}><code>File[]</code></a> |
|
||||
|
||||
---
|
||||
|
||||
### Logging
|
||||
|
||||
| Method | Path | Description | Response |
|
||||
| ------ | ------ | ------------------------------------------------------------ | --------- |
|
||||
| `POST` | `/log` | Write log entry. Body: `{ service, level, message, extra? }` | `boolean` |
|
||||
|
||||
---
|
||||
|
||||
### Agents
|
||||
|
||||
| Method | Path | Description | Response |
|
||||
| ------ | -------- | ------------------------- | ------------------------------------------- |
|
||||
| `GET` | `/agent` | List all available agents | <a href={typesUrl}><code>Agent[]</code></a> |
|
||||
|
||||
---
|
||||
|
||||
### TUI
|
||||
|
||||
| Method | Path | Description | Response |
|
||||
| ------ | ----------------------- | ------------------------------------------- | ---------------------- |
|
||||
| `POST` | `/tui/append-prompt` | Append text to the prompt | `boolean` |
|
||||
| `POST` | `/tui/open-help` | Open the help dialog | `boolean` |
|
||||
| `POST` | `/tui/open-sessions` | Open the session selector | `boolean` |
|
||||
| `POST` | `/tui/open-themes` | Open the theme selector | `boolean` |
|
||||
| `POST` | `/tui/open-models` | Open the model selector | `boolean` |
|
||||
| `POST` | `/tui/submit-prompt` | Submit the current prompt | `boolean` |
|
||||
| `POST` | `/tui/clear-prompt` | Clear the prompt | `boolean` |
|
||||
| `POST` | `/tui/execute-command` | Execute a command (`{ command }`) | `boolean` |
|
||||
| `POST` | `/tui/show-toast` | Show toast (`{ title?, message, variant }`) | `boolean` |
|
||||
| `GET` | `/tui/control/next` | Wait for the next control request | Control request object |
|
||||
| `POST` | `/tui/control/response` | Respond to a control request (`{ body }`) | `boolean` |
|
||||
|
||||
---
|
||||
|
||||
### Auth
|
||||
|
||||
| Method | Path | Description | Response |
|
||||
| ------ | ----------- | --------------------------------------------------------------- | --------- |
|
||||
| `PUT` | `/auth/:id` | Set authentication credentials. Body must match provider schema | `boolean` |
|
||||
|
||||
---
|
||||
|
||||
### Events
|
||||
|
||||
| Method | Path | Description | Response |
|
||||
| ------ | -------- | ----------------------------------------------------------------------------- | ------------------------- |
|
||||
| `GET` | `/event` | Server-sent events stream. First event is `server.connected`, then bus events | Server-sent events stream |
|
||||
|
||||
---
|
||||
|
||||
### Docs
|
||||
|
||||
| Method | Path | Description | Response |
|
||||
| ------ | ------ | -------------------------------------- | ------------------------------------------ |
|
||||
| `GET` | `/doc` | OpenAPI 3.1 specification | HTML page with OpenAPI spec |
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "opencode",
|
||||
"displayName": "opencode",
|
||||
"description": "opencode for VS Code",
|
||||
"version": "0.5.7",
|
||||
"version": "0.5.11",
|
||||
"publisher": "sst-dev",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
Reference in New Issue
Block a user