Update readme & use provided logging package

This commit is contained in:
Pietjan Oostra 2025-05-16 19:38:53 +02:00 • committed by Kujtim Hoxha
parent a2524a1d05
commit 12a98d6940
2 changed files with 54 additions and 9 deletions

View file

@ -100,6 +100,7 @@ You can configure OpenCode using environment variables:
| `AZURE_OPENAI_ENDPOINT` | For Azure OpenAI models | | `AZURE_OPENAI_ENDPOINT` | For Azure OpenAI models |
| `AZURE_OPENAI_API_KEY` | For Azure OpenAI models (optional when using Entra ID) | | `AZURE_OPENAI_API_KEY` | For Azure OpenAI models (optional when using Entra ID) |
| `AZURE_OPENAI_API_VERSION` | For Azure OpenAI models | | `AZURE_OPENAI_API_VERSION` | For Azure OpenAI models |
| `LOCAL_ENDPOINT` | For self-hosted models |
| `SHELL` | Default shell to use (if not specified in config) | | `SHELL` | Default shell to use (if not specified in config) |
### Shell Configuration ### Shell Configuration
@ -566,6 +567,35 @@ The AI assistant can access LSP features through the `diagnostics` tool, allowin
While the LSP client implementation supports the full LSP protocol (including completions, hover, definition, etc.), currently only diagnostics are exposed to the AI assistant. While the LSP client implementation supports the full LSP protocol (including completions, hover, definition, etc.), currently only diagnostics are exposed to the AI assistant.
## Using a self-hosted model provider
OpenCode can also load and use models from a self-hosted (OpenAI-like) provider.
This is useful for developers who want to experiment with custom models.
### Configuring a self-hosted provider
You can use a self-hosted model by setting the `LOCAL_ENDPOINT` environment variable.
This will cause OpenCode to load and use the models from the specified endpoint.
```bash
LOCAL_ENDPOINT=http://localhost:1235/v1
```
### Configuring a self-hosted model
You can also configure a self-hosted model in the configuration file under the `agents` section:
```json
{
"agents": {
"coder": {
"model": "local.granite-3.3-2b-instruct@q8_0",
"reasoningEffort": "high"
}
}
}
```
## Development ## Development
### Prerequisites ### Prerequisites

View file

@ -3,7 +3,6 @@ package models
import ( import (
"cmp" "cmp"
"encoding/json" "encoding/json"
"log/slog"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
@ -11,6 +10,7 @@ import (
"strings" "strings"
"unicode" "unicode"
"github.com/opencode-ai/opencode/internal/logging"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -25,7 +25,7 @@ func init() {
if endpoint := os.Getenv("LOCAL_ENDPOINT"); endpoint != "" { if endpoint := os.Getenv("LOCAL_ENDPOINT"); endpoint != "" {
localEndpoint, err := url.Parse(endpoint) localEndpoint, err := url.Parse(endpoint)
if err != nil { if err != nil {
slog.Debug("Failed to parse local endpoint", logging.Debug("Failed to parse local endpoint",
"error", err, "error", err,
"endpoint", endpoint, "endpoint", endpoint,
) )
@ -44,7 +44,7 @@ func init() {
} }
if len(models) == 0 { if len(models) == 0 {
slog.Debug("No local models found", logging.Debug("No local models found",
"endpoint", endpoint, "endpoint", endpoint,
) )
return return
@ -77,7 +77,7 @@ type localModel struct {
func listLocalModels(modelsEndpoint string) []localModel { func listLocalModels(modelsEndpoint string) []localModel {
res, err := http.Get(modelsEndpoint) res, err := http.Get(modelsEndpoint)
if err != nil { if err != nil {
slog.Debug("Failed to list local models", logging.Debug("Failed to list local models",
"error", err, "error", err,
"endpoint", modelsEndpoint, "endpoint", modelsEndpoint,
) )
@ -85,7 +85,7 @@ func listLocalModels(modelsEndpoint string) []localModel {
defer res.Body.Close() defer res.Body.Close()
if res.StatusCode != http.StatusOK { if res.StatusCode != http.StatusOK {
slog.Debug("Failed to list local models", logging.Debug("Failed to list local models",
"status", res.StatusCode, "status", res.StatusCode,
"endpoint", modelsEndpoint, "endpoint", modelsEndpoint,
) )
@ -93,7 +93,7 @@ func listLocalModels(modelsEndpoint string) []localModel {
var modelList localModelList var modelList localModelList
if err = json.NewDecoder(res.Body).Decode(&modelList); err != nil { if err = json.NewDecoder(res.Body).Decode(&modelList); err != nil {
slog.Debug("Failed to list local models", logging.Debug("Failed to list local models",
"error", err, "error", err,
"endpoint", modelsEndpoint, "endpoint", modelsEndpoint,
) )
@ -103,7 +103,7 @@ func listLocalModels(modelsEndpoint string) []localModel {
for _, model := range modelList.Data { for _, model := range modelList.Data {
if strings.HasSuffix(modelsEndpoint, lmStudioBetaModelsPath) { if strings.HasSuffix(modelsEndpoint, lmStudioBetaModelsPath) {
if model.Object != "model" || model.Type != "llm" { if model.Object != "model" || model.Type != "llm" {
slog.Debug("Skipping unsupported LMStudio model", logging.Debug("Skipping unsupported LMStudio model",
"endpoint", modelsEndpoint, "endpoint", modelsEndpoint,
"id", model.ID, "id", model.ID,
"object", model.Object, "object", model.Object,
@ -125,7 +125,7 @@ func loadLocalModels(models []localModel) {
model := convertLocalModel(m) model := convertLocalModel(m)
SupportedModels[model.ID] = model SupportedModels[model.ID] = model
if i == 1 || m.State == "loaded" { if i == 0 || m.State == "loaded" {
viper.SetDefault("agents.coder.model", model.ID) viper.SetDefault("agents.coder.model", model.ID)
viper.SetDefault("agents.summarizer.model", model.ID) viper.SetDefault("agents.summarizer.model", model.ID)
viper.SetDefault("agents.task.model", model.ID) viper.SetDefault("agents.task.model", model.ID)
@ -150,7 +150,19 @@ func convertLocalModel(model localModel) Model {
var modelInfoRegex = regexp.MustCompile(`(?i)^([a-z0-9]+)(?:[-_]?([rv]?\d[\.\d]*))?(?:[-_]?([a-z]+))?.*`) var modelInfoRegex = regexp.MustCompile(`(?i)^([a-z0-9]+)(?:[-_]?([rv]?\d[\.\d]*))?(?:[-_]?([a-z]+))?.*`)
func friendlyModelName(modelID string) string { func friendlyModelName(modelID string) string {
match := modelInfoRegex.FindStringSubmatch(modelID) mainID := modelID
tag := ""
if slash := strings.LastIndex(mainID, "/"); slash != -1 {
mainID = mainID[slash+1:]
}
if at := strings.Index(modelID, "@"); at != -1 {
mainID = modelID[:at]
tag = modelID[at+1:]
}
match := modelInfoRegex.FindStringSubmatch(mainID)
if match == nil { if match == nil {
return modelID return modelID
} }
@ -186,6 +198,9 @@ func friendlyModelName(modelID string) string {
if label != "" { if label != "" {
parts = append(parts, label) parts = append(parts, label)
} }
if tag != "" {
parts = append(parts, tag)
}
return strings.Join(parts, " ") return strings.Join(parts, " ")
} }