Add Docker Compose Lingma bootstrap support
Package the proxy for Docker Compose deployments and add Lingma bootstrap, session restore, and runtime status support for containerized remote usage. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -14,8 +14,10 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"lingma-ipc-proxy/internal/bootstrap"
|
||||
"lingma-ipc-proxy/internal/lingmaipc"
|
||||
"lingma-ipc-proxy/internal/remote"
|
||||
"lingma-ipc-proxy/internal/sessionbundle"
|
||||
"lingma-ipc-proxy/internal/toolemulation"
|
||||
)
|
||||
|
||||
@@ -35,24 +37,36 @@ const (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Host string
|
||||
Port int
|
||||
Backend BackendMode
|
||||
Transport lingmaipc.Transport
|
||||
Pipe string
|
||||
WebSocketURL string
|
||||
RemoteBaseURL string
|
||||
RemoteAuthFile string
|
||||
RemoteVersion string
|
||||
Cwd string
|
||||
CurrentFilePath string
|
||||
Mode string
|
||||
Model string
|
||||
ShellType string
|
||||
SessionMode SessionMode
|
||||
Timeout time.Duration
|
||||
RemoteFallbackEnabled bool
|
||||
RemoteFallbackModels []string
|
||||
Host string
|
||||
Port int
|
||||
Backend BackendMode
|
||||
Transport lingmaipc.Transport
|
||||
Pipe string
|
||||
WebSocketURL string
|
||||
RemoteBaseURL string
|
||||
RemoteAuthFile string
|
||||
RemoteVersion string
|
||||
Cwd string
|
||||
CurrentFilePath string
|
||||
Mode string
|
||||
Model string
|
||||
ShellType string
|
||||
SessionMode SessionMode
|
||||
Timeout time.Duration
|
||||
RemoteFallbackEnabled bool
|
||||
RemoteFallbackModels []string
|
||||
LingmaBootstrapEnabled bool
|
||||
LingmaSourceType string
|
||||
LingmaVSIXURL string
|
||||
LingmaMarketplacePublisher string
|
||||
LingmaMarketplaceExtension string
|
||||
LingmaBootstrapOutputDir string
|
||||
LingmaBinaryPath string
|
||||
LingmaBootstrapAlways bool
|
||||
LingmaForceRefresh bool
|
||||
LingmaWorkDir string
|
||||
LingmaSessionBundle string
|
||||
LingmaSessionBundleFile string
|
||||
}
|
||||
|
||||
type Image struct {
|
||||
@@ -127,12 +141,15 @@ type Model struct {
|
||||
}
|
||||
|
||||
type State struct {
|
||||
PipePath string `json:"pipe_path,omitempty"`
|
||||
Endpoint string `json:"endpoint,omitempty"`
|
||||
Transport string `json:"transport,omitempty"`
|
||||
Connected bool `json:"connected"`
|
||||
StickySessionID string `json:"sticky_session_id,omitempty"`
|
||||
SessionMode SessionMode `json:"session_mode"`
|
||||
PipePath string `json:"pipe_path,omitempty"`
|
||||
Endpoint string `json:"endpoint,omitempty"`
|
||||
Transport string `json:"transport,omitempty"`
|
||||
Connected bool `json:"connected"`
|
||||
StickySessionID string `json:"sticky_session_id,omitempty"`
|
||||
SessionMode SessionMode `json:"session_mode"`
|
||||
Bootstrap bootstrap.Result `json:"bootstrap,omitempty"`
|
||||
SessionBundle sessionbundle.Result `json:"session_bundle,omitempty"`
|
||||
RemoteAuth *remote.CredentialStatus `json:"remote_auth,omitempty"`
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
@@ -146,6 +163,8 @@ type Service struct {
|
||||
stickyModelID string
|
||||
modelMap map[string]string // official name -> internal id
|
||||
remoteClient *remote.Client
|
||||
bootstrapState bootstrap.Result
|
||||
sessionState sessionbundle.Result
|
||||
}
|
||||
|
||||
type promptRunResult struct {
|
||||
@@ -184,6 +203,24 @@ func New(cfg Config) *Service {
|
||||
if cfg.SessionMode == "" {
|
||||
cfg.SessionMode = SessionModeAuto
|
||||
}
|
||||
if strings.TrimSpace(cfg.LingmaSourceType) == "" {
|
||||
cfg.LingmaSourceType = "marketplace"
|
||||
}
|
||||
if strings.TrimSpace(cfg.LingmaMarketplacePublisher) == "" {
|
||||
cfg.LingmaMarketplacePublisher = "Alibaba-Cloud"
|
||||
}
|
||||
if strings.TrimSpace(cfg.LingmaMarketplaceExtension) == "" {
|
||||
cfg.LingmaMarketplaceExtension = "tongyi-lingma"
|
||||
}
|
||||
if strings.TrimSpace(cfg.LingmaBinaryPath) == "" {
|
||||
cfg.LingmaBinaryPath = filepath.Join(os.TempDir(), "lingma-proxy", "bin", "Lingma")
|
||||
}
|
||||
if strings.TrimSpace(cfg.LingmaBootstrapOutputDir) == "" {
|
||||
cfg.LingmaBootstrapOutputDir = filepath.Join(filepath.Dir(cfg.LingmaBinaryPath), "release")
|
||||
}
|
||||
if strings.TrimSpace(cfg.LingmaWorkDir) == "" {
|
||||
cfg.LingmaWorkDir = filepath.Join(filepath.Dir(filepath.Dir(cfg.LingmaBinaryPath)), ".lingma", "vscode", "sharedClientCache")
|
||||
}
|
||||
return &Service{cfg: cfg}
|
||||
}
|
||||
|
||||
@@ -210,6 +247,44 @@ func (s *Service) DefaultModel() string {
|
||||
return strings.TrimSpace(s.cfg.Model)
|
||||
}
|
||||
|
||||
func (s *Service) PrepareRuntime() error {
|
||||
s.mu.Lock()
|
||||
cfg := s.cfg
|
||||
s.mu.Unlock()
|
||||
|
||||
sessionState, err := sessionbundle.Restore(cfg.LingmaWorkDir, cfg.LingmaSessionBundle, cfg.LingmaSessionBundleFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if sessionState.Restored {
|
||||
if err := os.Setenv("LINGMA_CACHE_DIR", cfg.LingmaWorkDir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
bootstrapState, err := bootstrap.Ensure(bootstrap.Config{
|
||||
Enabled: cfg.LingmaBootstrapEnabled,
|
||||
SourceType: cfg.LingmaSourceType,
|
||||
VSIXURL: cfg.LingmaVSIXURL,
|
||||
MarketplacePublisher: cfg.LingmaMarketplacePublisher,
|
||||
MarketplaceExtension: cfg.LingmaMarketplaceExtension,
|
||||
OutputDir: cfg.LingmaBootstrapOutputDir,
|
||||
BinaryPath: cfg.LingmaBinaryPath,
|
||||
AlwaysRefresh: cfg.LingmaBootstrapAlways,
|
||||
ForceRefresh: cfg.LingmaForceRefresh,
|
||||
HTTPTimeout: 30 * time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
s.sessionState = sessionState
|
||||
s.bootstrapState = bootstrapState
|
||||
s.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) Warmup(ctx context.Context) error {
|
||||
if s.backend() == BackendRemote {
|
||||
return s.remoteClientLocked().Warmup(ctx)
|
||||
@@ -234,22 +309,26 @@ func contextWithOptionalTimeout(parent context.Context, timeout time.Duration) (
|
||||
func (s *Service) State() State {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
state := State{
|
||||
SessionMode: s.cfg.SessionMode,
|
||||
Bootstrap: s.bootstrapState,
|
||||
SessionBundle: s.sessionState,
|
||||
}
|
||||
if s.cfg.Backend == BackendRemote {
|
||||
return State{
|
||||
Endpoint: remote.ResolveBaseURL(s.cfg.RemoteBaseURL),
|
||||
Transport: "remote",
|
||||
Connected: s.remoteClient != nil,
|
||||
SessionMode: s.cfg.SessionMode,
|
||||
state.Endpoint = remote.ResolveBaseURL(s.cfg.RemoteBaseURL)
|
||||
state.Transport = "remote"
|
||||
if status, err := remote.LoadCredentialStatus(s.cfg.RemoteAuthFile); err == nil {
|
||||
state.RemoteAuth = &status
|
||||
state.Connected = status.Loaded && !status.Expired
|
||||
}
|
||||
return state
|
||||
}
|
||||
return State{
|
||||
PipePath: s.pipePath,
|
||||
Endpoint: s.endpoint,
|
||||
Transport: string(s.transport),
|
||||
Connected: s.client != nil,
|
||||
StickySessionID: s.stickySessionID,
|
||||
SessionMode: s.cfg.SessionMode,
|
||||
}
|
||||
state.PipePath = s.pipePath
|
||||
state.Endpoint = s.endpoint
|
||||
state.Transport = string(s.transport)
|
||||
state.Connected = s.client != nil
|
||||
state.StickySessionID = s.stickySessionID
|
||||
return state
|
||||
}
|
||||
|
||||
func (s *Service) ListModels(ctx context.Context) ([]Model, error) {
|
||||
|
||||
Reference in New Issue
Block a user