package main import ( "context" "encoding/json" "strings" "time" ) // RPC support for runtimes other than Ollama (vLLM, LM Studio). The Ollama paths in rpc.go stay as they are. // otherRuntime returns the runtime named in params, or nil for Ollama / unspecified. func otherRuntime(id string) modelRuntime { if id == "" || id == "ollama" { return nil } return runtimeByID(id) } func (r *rpcCtx) pullVia(rt modelRuntime, name string) { if err := rt.Ready(); err != nil { r.fail("%s is not ready: %v", rt.ID(), err) return } ctx, cancel := context.WithTimeout(context.Background(), 12*time.Hour) defer cancel() var lastStatus string var lastSend time.Time err := rt.Pull(ctx, name, func(status string, total, done int64) { if status != lastStatus || time.Since(lastSend) > 500*time.Millisecond { r.event(map[string]any{"status": status, "total": total, "completed": done}) lastStatus, lastSend = status, time.Now() } }) if err != nil { r.fail("download failed: %v", err) return } r.place(rt.ID(), name) r.ok(map[string]any{"name": name, "runtime": rt.ID()}) } func (r *rpcCtx) deleteVia(rt modelRuntime, name string) { if err := rt.Delete(name); err != nil { r.fail("%v", err) return } r.ok(map[string]any{"name": name}) } func (r *rpcCtx) unloadVia(rt modelRuntime, name string) { if err := rt.Unload(name); err != nil { r.fail("%v", err) return } r.ok(map[string]any{"name": name}) } // otherModelRows lists models of the non-Ollama runtimes for models.list. func otherModelRows() []map[string]any { rows := []map[string]any{} for _, rt := range allRuntimes { if rt.ID() == "ollama" || rt.Ready() != nil { continue } inst, err := rt.Installed() if err != nil { continue } loaded, _ := rt.Loaded() for name, size := range inst { _, isLoaded := loaded[name] loc, disk := modelLocation(loadNodeConfig(), rt.ID(), name) rows = append(rows, map[string]any{"name": name, "size": size, "runtime": rt.ID(), "loaded": isLoaded, "location": loc, "disk": disk}) } } return rows } // runtimeStatuses summarises every runtime for sys.info. func runtimeStatuses() map[string]any { out := map[string]any{} for _, rt := range allRuntimes { st := map[string]any{"ready": true} if err := rt.Ready(); err != nil { st["ready"] = false st["error"] = strings.TrimSpace(err.Error()) } out[rt.ID()] = st } return out } // parseRuntimeParams reads {name, runtime} from RPC params. func parseRuntimeParams(raw json.RawMessage) (name, runtimeID string, ok bool) { var p struct{ Name, Runtime string } if json.Unmarshal(raw, &p) != nil { return "", "", false } return p.Name, p.Runtime, true } // place moves a finished download to the external disk when the policy says so (see placeAfterPull). // A failed move is only reported: the model works where it was downloaded. func (r *rpcCtx) place(rtID, name string) { err := placeAfterPull(rtID, name, "auto", func(status string, total, done int64) { r.event(map[string]any{"status": status, "total": total, "completed": done}) }) if err != nil { r.event(map[string]any{"status": "could not move to the external disk: " + err.Error()}) } }