summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDax Raad <[email protected]>2025-06-18 22:59:42 -0400
committerDax Raad <[email protected]>2025-06-18 23:01:19 -0400
commitbd8c3cd0f1e30f5ed1cbf222415cef136edd14a9 (patch)
tree1e630283bb595c6e1f8ce21d83ae8ba99f24e51a
parente5e9b3e3c04df00db57d573d3cc0a029736184b1 (diff)
downloadopencode-bd8c3cd0f1e30f5ed1cbf222415cef136edd14a9.tar.gz
opencode-bd8c3cd0f1e30f5ed1cbf222415cef136edd14a9.zip
BREAKING CONFIG CHANGE
We have changed the config format yet again - but this should be the final time. You can see the readme for more details but the summary is - got rid of global providers config - got rid of global toml - global config is now in `~/.config/opencode/config.json` - it will be merged with any project level config
-rw-r--r--README.md94
-rw-r--r--packages/opencode/config.schema.json13
-rw-r--r--packages/opencode/src/config/config.ts90
-rw-r--r--packages/tui/internal/app/app.go13
-rw-r--r--packages/tui/internal/commands/command.go20
-rw-r--r--packages/tui/internal/tui/tui.go4
-rw-r--r--packages/tui/pkg/client/gen/openapi.json104
-rw-r--r--packages/tui/pkg/client/generated-client.go50
8 files changed, 269 insertions, 119 deletions
diff --git a/README.md b/README.md
index 6439b8ab5..8e42c2d02 100644
--- a/README.md
+++ b/README.md
@@ -61,66 +61,56 @@ The Models.dev dataset is also used to detect common environment variables like
If there are additional providers you want to use you can submit a PR to the [Models.dev repo](https://github.com/sst/models.dev). If configuring just for yourself check out the Config section below.
-### Global Config
+### Config
-Some basic configuration is available in the global config file.
+Config is optional and can be placed in the root of your repo or globally in `~/.config/opencode/config`. It can be checked in and shared with your team.
-```toml
-# ~/.config/opencode/config
-theme = "opencode"
-provider = "anthropic"
-model = "claude-sonnet-4-20250514"
-autoupdate = true
-
-keybinds.leader = "ctrl+x"
-keybinds.session_new = "<leader>n"
-keybinds.editor_open = "<leader>e"
+```json title="opencode.json"
+{
+ "$schema": "http://opencode.ai/config.json"
+ "theme": "opencode",
+ "model": "anthropic/claude-sonnet-4-20250514" // format is provider/model
+ "autoshare": false,
+ "autoupdate": true,
+}
```
#### Keybinds
-You can configure the keybinds in the global config file. (Note: values listed below are the defaults.)
-
-```toml
-# ~/.config/opencode/config
-
-[keybinds]
-leader = "ctrl+x"
-help = "<leader>h"
-editor_open = "<leader>e"
-session_new = "<leader>n"
-session_list = "<leader>l"
-session_share = "<leader>s"
-session_interrupt = "esc"
-session_compact = "<leader>c"
-tool_details = "<leader>d"
-model_list = "<leader>m"
-theme_list = "<leader>t"
-project_init = "<leader>i"
-input_clear = "ctrl+c"
-input_paste = "ctrl+v"
-input_submit = "enter"
-input_newline = "shift+enter"
-history_previous = "up"
-history_next = "down"
-messages_page_up = "pgup"
-messages_page_down = "pgdown"
-messages_half_page_up = "ctrl+alt+u"
-messages_half_page_down = "ctrl+alt+d"
-messages_previous = "ctrl+alt+k"
-messages_next = "ctrl+alt+j"
-messages_first = "ctrl+g"
-messages_last = "ctrl+alt+g"
-app_exit = "ctrl+c,<leader>q"
-```
-
-### Project Config
-
-Project configuration is optional. You can place an `opencode.json` file in the root of your repo and is meant to be checked in and shared with your team.
+You can configure custom keybinds, the values listed below are the defaults.
```json title="opencode.json"
{
- "$schema": "http://opencode.ai/config.json"
+ "$schema": "http://opencode.ai/config.json",
+ "keybinds": {
+ "leader": "ctrl+x",
+ "help": "<leader>h",
+ "editor_open": "<leader>e",
+ "session_new": "<leader>n",
+ "session_list": "<leader>l",
+ "session_share": "<leader>s",
+ "session_interrupt": "esc",
+ "session_compact": "<leader>c",
+ "tool_details": "<leader>d",
+ "model_list": "<leader>m",
+ "theme_list": "<leader>t",
+ "project_init": "<leader>i",
+ "input_clear": "ctrl+c",
+ "input_paste": "ctrl+v",
+ "input_submit": "enter",
+ "input_newline": "shift+enter",
+ "history_previous": "up",
+ "history_next": "down",
+ "messages_page_up": "pgup",
+ "messages_page_down": "pgdown",
+ "messages_half_page_up": "ctrl+alt+u",
+ "messages_half_page_down": "ctrl+alt+d",
+ "messages_previous": "ctrl+alt+k",
+ "messages_next": "ctrl+alt+j",
+ "messages_first": "ctrl+g",
+ "messages_last": "ctrl+alt+g",
+ "app_exit": "ctrl+c,<leader>q"
+ }
}
```
@@ -147,7 +137,7 @@ Project configuration is optional. You can place an `opencode.json` file in the
#### Providers
-You can use opencode with any provider listed at [here](https://ai-sdk.dev/providers/ai-sdk-providers). Be sure to specify the npm package to use to load the provider.
+You can use opencode with any provider listed at [here](https://ai-sdk.dev/providers/ai-sdk-providers). Be sure to specify the npm package to use to load the provider. Remember most popular providers are preloaded from [models.dev](https://models.dev)
```json title="opencode.json"
{
diff --git a/packages/opencode/config.schema.json b/packages/opencode/config.schema.json
index 48978fcb6..a57a4824e 100644
--- a/packages/opencode/config.schema.json
+++ b/packages/opencode/config.schema.json
@@ -95,16 +95,23 @@
"additionalProperties": false
},
"autoshare": {
- "type": "boolean"
+ "type": "boolean",
+ "description": "Share newly created sessions automatically"
},
"autoupdate": {
- "type": "boolean"
+ "type": "boolean",
+ "description": "Automatically update to the latest version"
},
"disabled_providers": {
"type": "array",
"items": {
"type": "string"
- }
+ },
+ "description": "Disable providers that are loaded automatically"
+ },
+ "model": {
+ "type": "string",
+ "description": "Model to use in the format of provider/model, eg anthropic/claude-2"
},
"provider": {
"type": "object",
diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts
index 565e84735..e1dc9ab75 100644
--- a/packages/opencode/src/config/config.ts
+++ b/packages/opencode/src/config/config.ts
@@ -64,44 +64,62 @@ export namespace Config {
export const Mcp = z.discriminatedUnion("type", [McpLocal, McpRemote])
export type Mcp = z.infer<typeof Mcp>
+ export const Keybinds = z
+ .object({
+ leader: z.string().optional(),
+ help: z.string().optional(),
+ editor_open: z.string().optional(),
+ session_new: z.string().optional(),
+ session_list: z.string().optional(),
+ session_share: z.string().optional(),
+ session_interrupt: z.string().optional(),
+ session_compact: z.string().optional(),
+ tool_details: z.string().optional(),
+ model_list: z.string().optional(),
+ theme_list: z.string().optional(),
+ project_init: z.string().optional(),
+ input_clear: z.string().optional(),
+ input_paste: z.string().optional(),
+ input_submit: z.string().optional(),
+ input_newline: z.string().optional(),
+ history_previous: z.string().optional(),
+ history_next: z.string().optional(),
+ messages_page_up: z.string().optional(),
+ messages_page_down: z.string().optional(),
+ messages_half_page_up: z.string().optional(),
+ messages_half_page_down: z.string().optional(),
+ messages_previous: z.string().optional(),
+ messages_next: z.string().optional(),
+ messages_first: z.string().optional(),
+ messages_last: z.string().optional(),
+ app_exit: z.string().optional(),
+ })
+ .openapi({
+ ref: "Config.Keybinds",
+ })
export const Info = z
.object({
$schema: z.string().optional(),
theme: z.string().optional(),
- keybinds: z
- .object({
- leader: z.string().optional(),
- help: z.string().optional(),
- editor_open: z.string().optional(),
- session_new: z.string().optional(),
- session_list: z.string().optional(),
- session_share: z.string().optional(),
- session_interrupt: z.string().optional(),
- session_compact: z.string().optional(),
- tool_details: z.string().optional(),
- model_list: z.string().optional(),
- theme_list: z.string().optional(),
- project_init: z.string().optional(),
- input_clear: z.string().optional(),
- input_paste: z.string().optional(),
- input_submit: z.string().optional(),
- input_newline: z.string().optional(),
- history_previous: z.string().optional(),
- history_next: z.string().optional(),
- messages_page_up: z.string().optional(),
- messages_page_down: z.string().optional(),
- messages_half_page_up: z.string().optional(),
- messages_half_page_down: z.string().optional(),
- messages_previous: z.string().optional(),
- messages_next: z.string().optional(),
- messages_first: z.string().optional(),
- messages_last: z.string().optional(),
- app_exit: z.string().optional(),
- })
+ keybinds: Keybinds.optional(),
+ autoshare: z
+ .boolean()
+ .optional()
+ .describe("Share newly created sessions automatically"),
+ autoupdate: z
+ .boolean()
+ .optional()
+ .describe("Automatically update to the latest version"),
+ disabled_providers: z
+ .array(z.string())
+ .optional()
+ .describe("Disable providers that are loaded automatically"),
+ model: z
+ .string()
+ .describe(
+ "Model to use in the format of provider/model, eg anthropic/claude-2",
+ )
.optional(),
- autoshare: z.boolean().optional(),
- autoupdate: z.boolean().optional(),
- disabled_providers: z.array(z.string()).optional(),
provider: z
.record(
ModelsDev.Provider.partial().extend({
@@ -130,9 +148,9 @@ export namespace Config {
},
})
.then(async (mod) => {
- delete mod.default.provider
- delete mod.default.model
- result = mergeDeep(result, mod.default)
+ const { provider, model, ...rest } = mod.default
+ if (provider && model) result.model = `${provider}/${model}`
+ result = mergeDeep(result, rest)
await Bun.write(
path.join(Global.Path.config, "config.json"),
JSON.stringify(result, null, 2),
diff --git a/packages/tui/internal/app/app.go b/packages/tui/internal/app/app.go
index eb4bd2dad..72e6ad5eb 100644
--- a/packages/tui/internal/app/app.go
+++ b/packages/tui/internal/app/app.go
@@ -5,6 +5,7 @@ import (
"fmt"
"path/filepath"
"sort"
+ "strings"
"log/slog"
@@ -61,8 +62,10 @@ func New(
}
configInfo := configResponse.JSON200
if configInfo.Keybinds == nil {
- keybinds := make(map[string]string)
- keybinds["leader"] = "ctrl+x"
+ leader := "ctrl+x"
+ keybinds := client.ConfigKeybinds{
+ Leader: &leader,
+ }
configInfo.Keybinds = &keybinds
}
@@ -76,6 +79,12 @@ func New(
if configInfo.Theme != nil {
appState.Theme = *configInfo.Theme
}
+ if configInfo.Model != nil {
+ splits := strings.Split(*configInfo.Model, "/")
+ appState.Provider = splits[0]
+ appState.Model = strings.Join(splits[1:], "/")
+ }
+
if appState.Theme != "" {
theme.SetTheme(appState.Theme)
}
diff --git a/packages/tui/internal/commands/command.go b/packages/tui/internal/commands/command.go
index 6f628ae31..12378c0b2 100644
--- a/packages/tui/internal/commands/command.go
+++ b/packages/tui/internal/commands/command.go
@@ -1,6 +1,7 @@
package commands
import (
+ "encoding/json"
"slices"
"strings"
@@ -106,17 +107,6 @@ func (k Command) Matches(msg tea.KeyPressMsg, leader bool) bool {
return false
}
-func (k Command) FromConfig(config *client.ConfigInfo) Command {
- if config.Keybinds == nil {
- return k
- }
- keybinds := *config.Keybinds
- if keybind, ok := keybinds[string(k.Name)]; ok {
- k.Keybindings = parseBindings(keybind)
- }
- return k
-}
-
func parseBindings(bindings ...string) []Keybinding {
var parsedBindings []Keybinding
for _, binding := range bindings {
@@ -278,8 +268,14 @@ func LoadFromConfig(config *client.ConfigInfo) CommandRegistry {
},
}
registry := make(CommandRegistry)
+ keybinds := map[string]string{}
+ marshalled, _ := json.Marshal(*config.Keybinds)
+ json.Unmarshal(marshalled, &keybinds)
for _, command := range defaults {
- registry[command.Name] = command.FromConfig(config)
+ if keybind, ok := keybinds[string(command.Name)]; ok {
+ command.Keybindings = parseBindings(keybind)
+ }
+ registry[command.Name] = command
}
return registry
}
diff --git a/packages/tui/internal/tui/tui.go b/packages/tui/internal/tui/tui.go
index 5d63a161a..25af7523d 100644
--- a/packages/tui/internal/tui/tui.go
+++ b/packages/tui/internal/tui/tui.go
@@ -509,8 +509,8 @@ func NewModel(app *app.App) tea.Model {
messagesContainer := layout.NewContainer(messages)
var leaderBinding *key.Binding
- if leader, ok := (*app.Configg.Keybinds)["leader"]; ok {
- binding := key.NewBinding(key.WithKeys(leader))
+ if (*app.Configg.Keybinds).Leader != nil {
+ binding := key.NewBinding(key.WithKeys(*app.Configg.Keybinds.Leader))
leaderBinding = &binding
}
diff --git a/packages/tui/pkg/client/gen/openapi.json b/packages/tui/pkg/client/gen/openapi.json
index ea055a64f..862a84d7d 100644
--- a/packages/tui/pkg/client/gen/openapi.json
+++ b/packages/tui/pkg/client/gen/openapi.json
@@ -1397,22 +1397,26 @@
"type": "string"
},
"keybinds": {
- "type": "object",
- "additionalProperties": {
- "type": "string"
- }
+ "$ref": "#/components/schemas/Config.Keybinds"
},
"autoshare": {
- "type": "boolean"
+ "type": "boolean",
+ "description": "Share newly created sessions automatically"
},
"autoupdate": {
- "type": "boolean"
+ "type": "boolean",
+ "description": "Automatically update to the latest version"
},
"disabled_providers": {
"type": "array",
"items": {
"type": "string"
- }
+ },
+ "description": "Disable providers that are loaded automatically"
+ },
+ "model": {
+ "type": "string",
+ "description": "Model to use in the format of provider/model, eg anthropic/claude-2"
},
"provider": {
"type": "object",
@@ -1528,6 +1532,92 @@
}
}
},
+ "Config.Keybinds": {
+ "type": "object",
+ "properties": {
+ "leader": {
+ "type": "string"
+ },
+ "help": {
+ "type": "string"
+ },
+ "editor_open": {
+ "type": "string"
+ },
+ "session_new": {
+ "type": "string"
+ },
+ "session_list": {
+ "type": "string"
+ },
+ "session_share": {
+ "type": "string"
+ },
+ "session_interrupt": {
+ "type": "string"
+ },
+ "session_compact": {
+ "type": "string"
+ },
+ "tool_details": {
+ "type": "string"
+ },
+ "model_list": {
+ "type": "string"
+ },
+ "theme_list": {
+ "type": "string"
+ },
+ "project_init": {
+ "type": "string"
+ },
+ "input_clear": {
+ "type": "string"
+ },
+ "input_paste": {
+ "type": "string"
+ },
+ "input_submit": {
+ "type": "string"
+ },
+ "input_newline": {
+ "type": "string"
+ },
+ "history_previous": {
+ "type": "string"
+ },
+ "history_next": {
+ "type": "string"
+ },
+ "messages_page_up": {
+ "type": "string"
+ },
+ "messages_page_down": {
+ "type": "string"
+ },
+ "messages_half_page_up": {
+ "type": "string"
+ },
+ "messages_half_page_down": {
+ "type": "string"
+ },
+ "messages_previous": {
+ "type": "string"
+ },
+ "messages_next": {
+ "type": "string"
+ },
+ "messages_first": {
+ "type": "string"
+ },
+ "messages_last": {
+ "type": "string"
+ },
+ "app_exit": {
+ "type": "string"
+ }
+ }
+ },
"Provider.Info": {
"type": "object",
"properties": {
diff --git a/packages/tui/pkg/client/generated-client.go b/packages/tui/pkg/client/generated-client.go
index c2ecffe94..15acb5488 100644
--- a/packages/tui/pkg/client/generated-client.go
+++ b/packages/tui/pkg/client/generated-client.go
@@ -41,13 +41,22 @@ type AppInfo struct {
// ConfigInfo defines model for Config.Info.
type ConfigInfo struct {
- Schema *string `json:"$schema,omitempty"`
- Autoshare *bool `json:"autoshare,omitempty"`
- Autoupdate *bool `json:"autoupdate,omitempty"`
+ Schema *string `json:"$schema,omitempty"`
+
+ // Autoshare Share newly created sessions automatically
+ Autoshare *bool `json:"autoshare,omitempty"`
+
+ // Autoupdate Automatically update to the latest version
+ Autoupdate *bool `json:"autoupdate,omitempty"`
+
+ // DisabledProviders Disable providers that are loaded automatically
DisabledProviders *[]string `json:"disabled_providers,omitempty"`
- Keybinds *map[string]string `json:"keybinds,omitempty"`
+ Keybinds *ConfigKeybinds `json:"keybinds,omitempty"`
Mcp *map[string]ConfigInfo_Mcp_AdditionalProperties `json:"mcp,omitempty"`
- Provider *map[string]struct {
+
+ // Model Model to use in the format of provider/model, eg anthropic/claude-2
+ Model *string `json:"model,omitempty"`
+ Provider *map[string]struct {
Api *string `json:"api,omitempty"`
Env *[]string `json:"env,omitempty"`
Id *string `json:"id,omitempty"`
@@ -80,6 +89,37 @@ type ConfigInfo_Mcp_AdditionalProperties struct {
union json.RawMessage
}
+// ConfigKeybinds defines model for Config.Keybinds.
+type ConfigKeybinds struct {
+ AppExit *string `json:"app_exit,omitempty"`
+ EditorOpen *string `json:"editor_open,omitempty"`
+ Help *string `json:"help,omitempty"`
+ HistoryNext *string `json:"history_next,omitempty"`
+ HistoryPrevious *string `json:"history_previous,omitempty"`
+ InputClear *string `json:"input_clear,omitempty"`
+ InputNewline *string `json:"input_newline,omitempty"`
+ InputPaste *string `json:"input_paste,omitempty"`
+ InputSubmit *string `json:"input_submit,omitempty"`
+ Leader *string `json:"leader,omitempty"`
+ MessagesFirst *string `json:"messages_first,omitempty"`
+ MessagesHalfPageDown *string `json:"messages_half_page_down,omitempty"`
+ MessagesHalfPageUp *string `json:"messages_half_page_up,omitempty"`
+ MessagesLast *string `json:"messages_last,omitempty"`
+ MessagesNext *string `json:"messages_next,omitempty"`
+ MessagesPageDown *string `json:"messages_page_down,omitempty"`
+ MessagesPageUp *string `json:"messages_page_up,omitempty"`
+ MessagesPrevious *string `json:"messages_previous,omitempty"`
+ ModelList *string `json:"model_list,omitempty"`
+ ProjectInit *string `json:"project_init,omitempty"`
+ SessionCompact *string `json:"session_compact,omitempty"`
+ SessionInterrupt *string `json:"session_interrupt,omitempty"`
+ SessionList *string `json:"session_list,omitempty"`
+ SessionNew *string `json:"session_new,omitempty"`
+ SessionShare *string `json:"session_share,omitempty"`
+ ThemeList *string `json:"theme_list,omitempty"`
+ ToolDetails *string `json:"tool_details,omitempty"`
+}
+
// ConfigMcpLocal defines model for Config.McpLocal.
type ConfigMcpLocal struct {
Command []string `json:"command"`