Chapter 4: Model Providers and Runtime Operations
April 13, 2026 ยท View on GitHub
Welcome to Chapter 4: Model Providers and Runtime Operations. In this part of OpenCode AI Legacy Tutorial: Archived Terminal Agent Workflows and Migration to Crush, you will build an intuitive mental model first, then move into concrete implementation details and practical production tradeoffs.
This chapter covers model/provider routing and runtime controls in legacy mode.
Learning Goals
- configure provider credentials and fallback paths
- map model choice to task quality/latency needs
- constrain shell/tool runtime behavior safely
- document environment assumptions for repeatability
Runtime Considerations
- keep provider keys scoped and rotated
- pin model IDs used in legacy automation
- validate shell config and command safety boundaries
Source References
Summary
You now have a stable runtime configuration model for legacy operations.
Next: Chapter 5: Interactive and Non-Interactive Workflows
Source Code Walkthrough
internal/lsp/client.go
The openKeyConfigFiles function in internal/lsp/client.go handles a key part of this chapter's functionality:
logging.Debug("TypeScript-like server detected, opening key configuration files")
}
c.openKeyConfigFiles(ctx)
}
for {
select {
case <-ctx.Done():
c.SetServerState(StateError)
return fmt.Errorf("timeout waiting for LSP server to be ready")
case <-ticker.C:
// Try a ping method appropriate for this server type
err := c.pingServerByType(ctx, serverType)
if err == nil {
// Server responded successfully
c.SetServerState(StateReady)
if cnf.DebugLSP {
logging.Debug("LSP server is ready")
}
return nil
} else {
logging.Debug("LSP server not ready yet", "error", err, "serverType", serverType)
}
if cnf.DebugLSP {
logging.Debug("LSP server not ready yet", "error", err, "serverType", serverType)
}
}
}
}
// ServerType represents the type of LSP server
This function is important because it defines how OpenCode AI Legacy Tutorial: Archived Terminal Agent Workflows and Migration to Crush implements the patterns covered in this chapter.
internal/lsp/client.go
The pingServerByType function in internal/lsp/client.go handles a key part of this chapter's functionality:
case <-ticker.C:
// Try a ping method appropriate for this server type
err := c.pingServerByType(ctx, serverType)
if err == nil {
// Server responded successfully
c.SetServerState(StateReady)
if cnf.DebugLSP {
logging.Debug("LSP server is ready")
}
return nil
} else {
logging.Debug("LSP server not ready yet", "error", err, "serverType", serverType)
}
if cnf.DebugLSP {
logging.Debug("LSP server not ready yet", "error", err, "serverType", serverType)
}
}
}
}
// ServerType represents the type of LSP server
type ServerType int
const (
ServerTypeUnknown ServerType = iota
ServerTypeGo
ServerTypeTypeScript
ServerTypeRust
ServerTypePython
ServerTypeGeneric
)
This function is important because it defines how OpenCode AI Legacy Tutorial: Archived Terminal Agent Workflows and Migration to Crush implements the patterns covered in this chapter.
internal/lsp/client.go
The pingTypeScriptServer function in internal/lsp/client.go handles a key part of this chapter's functionality:
case ServerTypeTypeScript:
// For TypeScript, try a document symbol request on an open file
return c.pingTypeScriptServer(ctx)
case ServerTypeGo:
// For Go, workspace/symbol works well
return c.pingWithWorkspaceSymbol(ctx)
case ServerTypeRust:
// For Rust, workspace/symbol works well
return c.pingWithWorkspaceSymbol(ctx)
default:
// Default ping method
return c.pingWithWorkspaceSymbol(ctx)
}
}
// pingTypeScriptServer tries to ping a TypeScript server with appropriate methods
func (c *Client) pingTypeScriptServer(ctx context.Context) error {
// First try workspace/symbol which works for many servers
if err := c.pingWithWorkspaceSymbol(ctx); err == nil {
return nil
}
// If that fails, try to find an open file and request document symbols
c.openFilesMu.RLock()
defer c.openFilesMu.RUnlock()
// If we have any open files, try to get document symbols for one
for uri := range c.openFiles {
filePath := strings.TrimPrefix(uri, "file://")
if strings.HasSuffix(filePath, ".ts") || strings.HasSuffix(filePath, ".js") ||
strings.HasSuffix(filePath, ".tsx") || strings.HasSuffix(filePath, ".jsx") {
var symbols []protocol.DocumentSymbol
This function is important because it defines how OpenCode AI Legacy Tutorial: Archived Terminal Agent Workflows and Migration to Crush implements the patterns covered in this chapter.
internal/lsp/client.go
The openTypeScriptFiles function in internal/lsp/client.go handles a key part of this chapter's functionality:
// Also find and open a few TypeScript files to help the server initialize
c.openTypeScriptFiles(ctx, workDir)
case ServerTypeGo:
filesToOpen = []string{
filepath.Join(workDir, "go.mod"),
filepath.Join(workDir, "go.sum"),
}
case ServerTypeRust:
filesToOpen = []string{
filepath.Join(workDir, "Cargo.toml"),
filepath.Join(workDir, "Cargo.lock"),
}
}
// Try to open each file, ignoring errors if they don't exist
for _, file := range filesToOpen {
if _, err := os.Stat(file); err == nil {
// File exists, try to open it
if err := c.OpenFile(ctx, file); err != nil {
logging.Debug("Failed to open key config file", "file", file, "error", err)
} else {
logging.Debug("Opened key config file for initialization", "file", file)
}
}
}
}
// pingServerByType sends a ping request appropriate for the server type
func (c *Client) pingServerByType(ctx context.Context, serverType ServerType) error {
switch serverType {
case ServerTypeTypeScript:
This function is important because it defines how OpenCode AI Legacy Tutorial: Archived Terminal Agent Workflows and Migration to Crush implements the patterns covered in this chapter.
How These Components Connect
flowchart TD
A[openKeyConfigFiles]
B[pingServerByType]
C[pingTypeScriptServer]
D[openTypeScriptFiles]
E[shouldSkipDir]
A --> B
B --> C
C --> D
D --> E