openclaw.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. package llm_container
  2. import (
  3. "context"
  4. "database/sql"
  5. "encoding/base64"
  6. "fmt"
  7. "hash/fnv"
  8. "strings"
  9. "yunion.io/x/jsonutils"
  10. "yunion.io/x/pkg/errors"
  11. commonapi "yunion.io/x/onecloud/pkg/apis"
  12. computeapi "yunion.io/x/onecloud/pkg/apis/compute"
  13. api "yunion.io/x/onecloud/pkg/apis/llm"
  14. "yunion.io/x/onecloud/pkg/llm/models"
  15. "yunion.io/x/onecloud/pkg/mcclient"
  16. )
  17. // coollabsio/openclaw docker-compose: openclaw (main) + browser (CDP sidecar for /browser/)
  18. // See: https://github.com/coollabsio/openclaw/blob/main/docker-compose.yml
  19. const (
  20. // openclawContainerName = "openclaw"
  21. // browserContainerName = "browser"
  22. // openclawBrowserImage = "registry.cn-beijing.aliyuncs.com/cloudpods/openclaw-browser:latest"
  23. openclawDataDir = "/data"
  24. desktopConfigDir = "/config"
  25. homeDir = "/home/"
  26. // openclawBrowserCDPPort = "9222"
  27. )
  28. func openclawFixed9DigitPassword(llmId string) string {
  29. h := fnv.New64a()
  30. _, _ = h.Write([]byte(llmId))
  31. x := h.Sum64()
  32. // 固定 9 位,字母数字混合,并保证至少包含 1 个字母
  33. const alpha = "abcdefghijklmnopqrstuvwxyz"
  34. const base36 = "0123456789abcdefghijklmnopqrstuvwxyz"
  35. out := make([]byte, 9)
  36. out[0] = alpha[x%26]
  37. for i := 1; i < len(out); i++ {
  38. // xorshift64* 生成稳定伪随机序列
  39. x ^= x >> 12
  40. x ^= x << 25
  41. x ^= x >> 27
  42. x *= 2685821657736338717
  43. out[i] = base36[x%36]
  44. }
  45. return string(out)
  46. }
  47. func appendCredentialEnvs(envs []*commonapi.ContainerKeyValue, cred *api.LLMSpecCredential) []*commonapi.ContainerKeyValue {
  48. if cred == nil {
  49. return envs
  50. }
  51. for _, key := range cred.ExportKeys {
  52. envs = append(envs, &commonapi.ContainerKeyValue{
  53. Key: key,
  54. ValueFrom: &commonapi.ContainerValueSource{
  55. Credential: &commonapi.ContainerValueSourceCredential{
  56. Id: cred.Id,
  57. Key: key,
  58. },
  59. },
  60. })
  61. }
  62. return envs
  63. }
  64. func init() {
  65. models.RegisterLLMContainerDriver(newOpenClaw())
  66. }
  67. type openclaw struct {
  68. baseDriver
  69. }
  70. func newOpenClaw() models.ILLMContainerDriver {
  71. return &openclaw{baseDriver: newBaseDriver(api.LLM_CONTAINER_OPENCLAW)}
  72. }
  73. func (c *openclaw) GetSpec(sku *models.SLLMSku) interface{} {
  74. if sku == nil || sku.LLMSpec == nil {
  75. return nil
  76. }
  77. return sku.LLMSpec.OpenClaw
  78. }
  79. // mergeOpenClaw merges llm and sku OpenClaw specs; llm takes priority, use sku when llm field is unset (nil or empty).
  80. func mergeOpenClaw(llm, sku *api.LLMSpecOpenClaw) *api.LLMSpecOpenClaw {
  81. if llm == nil {
  82. if sku == nil {
  83. return nil
  84. }
  85. return copyOpenClaw(sku)
  86. }
  87. if sku == nil {
  88. return copyOpenClaw(llm)
  89. }
  90. out := &api.LLMSpecOpenClaw{}
  91. if len(llm.Providers) > 0 {
  92. out.Providers = make([]*api.LLMSpecOpenClawProvider, len(llm.Providers))
  93. copy(out.Providers, llm.Providers)
  94. } else if len(sku.Providers) > 0 {
  95. out.Providers = make([]*api.LLMSpecOpenClawProvider, len(sku.Providers))
  96. copy(out.Providers, sku.Providers)
  97. }
  98. if len(llm.Channels) > 0 {
  99. out.Channels = make([]*api.LLMSpecOpenClawChannel, len(llm.Channels))
  100. copy(out.Channels, llm.Channels)
  101. } else if len(sku.Channels) > 0 {
  102. out.Channels = make([]*api.LLMSpecOpenClawChannel, len(sku.Channels))
  103. copy(out.Channels, sku.Channels)
  104. }
  105. if llm.WorkspaceTemplates != nil && (llm.WorkspaceTemplates.AgentsMD != "" || llm.WorkspaceTemplates.SoulMD != "" || llm.WorkspaceTemplates.UserMD != "") {
  106. out.WorkspaceTemplates = &api.LLMSpecOpenClawWorkspaceTemplates{
  107. AgentsMD: llm.WorkspaceTemplates.AgentsMD,
  108. SoulMD: llm.WorkspaceTemplates.SoulMD,
  109. UserMD: llm.WorkspaceTemplates.UserMD,
  110. }
  111. } else if sku.WorkspaceTemplates != nil {
  112. out.WorkspaceTemplates = &api.LLMSpecOpenClawWorkspaceTemplates{
  113. AgentsMD: sku.WorkspaceTemplates.AgentsMD,
  114. SoulMD: sku.WorkspaceTemplates.SoulMD,
  115. UserMD: sku.WorkspaceTemplates.UserMD,
  116. }
  117. }
  118. return out
  119. }
  120. func copyOpenClaw(s *api.LLMSpecOpenClaw) *api.LLMSpecOpenClaw {
  121. if s == nil {
  122. return nil
  123. }
  124. out := &api.LLMSpecOpenClaw{}
  125. if len(s.Providers) > 0 {
  126. out.Providers = make([]*api.LLMSpecOpenClawProvider, len(s.Providers))
  127. copy(out.Providers, s.Providers)
  128. }
  129. if len(s.Channels) > 0 {
  130. out.Channels = make([]*api.LLMSpecOpenClawChannel, len(s.Channels))
  131. copy(out.Channels, s.Channels)
  132. }
  133. if s.WorkspaceTemplates != nil {
  134. out.WorkspaceTemplates = &api.LLMSpecOpenClawWorkspaceTemplates{
  135. AgentsMD: s.WorkspaceTemplates.AgentsMD,
  136. SoulMD: s.WorkspaceTemplates.SoulMD,
  137. UserMD: s.WorkspaceTemplates.UserMD,
  138. }
  139. }
  140. return out
  141. }
  142. func (c *openclaw) GetEffectiveSpec(llm *models.SLLM, sku *models.SLLMSku) interface{} {
  143. if sku == nil || sku.LLMSpec == nil {
  144. return llm.LLMSpec.OpenClaw
  145. }
  146. var llmOC *api.LLMSpecOpenClaw
  147. if llm != nil && llm.LLMSpec != nil {
  148. llmOC = llm.LLMSpec.OpenClaw
  149. }
  150. return mergeOpenClaw(llmOC, sku.LLMSpec.OpenClaw)
  151. }
  152. func (c *openclaw) StartLLM(ctx context.Context, userCred mcclient.TokenCredential, llm *models.SLLM) error {
  153. // lc, err := llm.GetLLMContainer()
  154. // if err != nil {
  155. // return errors.Wrap(err, "get llm container")
  156. // }
  157. // // 启动 openclaw gateway
  158. // cmd := fmt.Sprintf("/app/scripts/entrypoint-gui.sh")
  159. // _, err = exec(ctx, lc.CmpId, cmd, 30)
  160. // if err != nil {
  161. // return errors.Wrap(err, "exec start openclaw gateway")
  162. // }
  163. return nil
  164. }
  165. func (c *openclaw) GetContainerSpec(ctx context.Context, llm *models.SLLM, image *models.SLLMImage, sku *models.SLLMSku, props []string, devices []computeapi.SIsolatedDevice, diskId string) *computeapi.PodContainerCreateInput {
  166. // Multi-container: use GetContainerSpecs
  167. return nil
  168. }
  169. // func (c *openclaw) GetContainerSpecs(ctx context.Context, llm *models.SLLM, image *models.SLLMImage, sku *models.SLLMSku, props []string, devices []computeapi.SIsolatedDevice, diskId string) []*computeapi.PodContainerCreateInput {
  170. // diskIndex := 0
  171. // // 1. Browser sidecar: CDP on 9222, persistent /config, shm 2g
  172. // browserVols := []*commonapi.ContainerVolumeMount{
  173. // {
  174. // Disk: &commonapi.ContainerVolumeMountDisk{
  175. // Index: &diskIndex,
  176. // SubDirectory: browserStorageDir,
  177. // },
  178. // Type: commonapi.CONTAINER_VOLUME_MOUNT_TYPE_DISK,
  179. // MountPath: browserConfigDir,
  180. // },
  181. // }
  182. // browserSpec := computeapi.ContainerSpec{
  183. // ContainerSpec: commonapi.ContainerSpec{
  184. // Image: openclawBrowserImage,
  185. // EnableLxcfs: true,
  186. // AlwaysRestart: true,
  187. // ShmSizeMB: 2048, // 2g for Chrome
  188. // Envs: []*commonapi.ContainerKeyValue{
  189. // {Key: "PUID", Value: "1000"},
  190. // {Key: "PGID", Value: "1000"},
  191. // {Key: "TZ", Value: "Etc/UTC"},
  192. // {Key: "CHROME_CLI", Value: "--remote-debugging-port=" + openclawBrowserCDPPort},
  193. // },
  194. // },
  195. // VolumeMounts: browserVols,
  196. // }
  197. // // 2. OpenClaw main: nginx :8080 -> gateway :18789, /data, depends on browser
  198. // openclawVols := []*commonapi.ContainerVolumeMount{
  199. // {
  200. // Disk: &commonapi.ContainerVolumeMountDisk{
  201. // Index: &diskIndex,
  202. // SubDirectory: "data",
  203. // },
  204. // Type: commonapi.CONTAINER_VOLUME_MOUNT_TYPE_DISK,
  205. // MountPath: openclawDataDir,
  206. // },
  207. // }
  208. // openclawSpec := computeapi.ContainerSpec{
  209. // ContainerSpec: commonapi.ContainerSpec{
  210. // Image: image.ToContainerImage(),
  211. // ImageCredentialId: image.CredentialId,
  212. // EnableLxcfs: true,
  213. // AlwaysRestart: true,
  214. // DependsOn: []string{fmt.Sprintf("%s-%s", llm.GetName(), browserContainerName)},
  215. // Envs: []*commonapi.ContainerKeyValue{
  216. // // Provider
  217. // {Key: "MOONSHOT_API_KEY", Value: "sk-9taa32DcGGQliadQTEcZfpMUL9LCAnZVfyE6hKWPUMWEofJ8"},
  218. // {Key: "OPENCLAW_PRIMARY_MODEL", Value: "moonshot/kimi-k2.5"},
  219. // // Auth
  220. // {Key: "AUTH_USERNAME", Value: "admin"},
  221. // {Key: "AUTH_PASSWORD", Value: "admin@123"},
  222. // {Key: "OPENCLAW_GATEWAY_TOKEN", Value: "90d42cfc7a925201a27b61ce9b6403693629d2a18094a596"},
  223. // // Browser sidecar
  224. // {Key: "BROWSER_CDP_URL", Value: "http://localhost" + ":" + openclawBrowserCDPPort},
  225. // {Key: "BROWSER_DEFAULT_PROFILE", Value: "openclaw"},
  226. // {Key: "BROWSER_EVALUATE_ENABLED", Value: "true"},
  227. // },
  228. // },
  229. // VolumeMounts: openclawVols,
  230. // }
  231. // return []*computeapi.PodContainerCreateInput{
  232. // {Name: fmt.Sprintf("%s-%s", llm.GetName(), browserContainerName), ContainerSpec: browserSpec},
  233. // {Name: fmt.Sprintf("%s-%s", llm.GetName(), openclawContainerName), ContainerSpec: openclawSpec},
  234. // }
  235. // }
  236. func (c *openclaw) getOpenClawBaseConfig(llm *models.SLLM) *api.OpenClawConfig {
  237. return &api.OpenClawConfig{
  238. Browser: &api.OpenClawConfigBrowser{
  239. Enabled: true,
  240. DefaultProfile: "openclaw",
  241. SSRFPolicy: map[string]interface{}{
  242. "dangerouslyAllowPrivateNetwork": true,
  243. },
  244. Headless: false,
  245. NoSandbox: false,
  246. },
  247. Agents: &api.OpenClawConfigAgents{
  248. "defaults": &api.OpenClawConfigAgent{
  249. // TODO: 支持从 llm spec 里面自动选择
  250. ImageModel: &api.OpenClawConfigAgentModel{
  251. Primary: "moonshot/kimi-k2.5",
  252. },
  253. },
  254. },
  255. }
  256. }
  257. func (c *openclaw) GetContainerSpecs(ctx context.Context, llm *models.SLLM, image *models.SLLMImage, sku *models.SLLMSku, props []string, devices []computeapi.SIsolatedDevice, diskId string) []*computeapi.PodContainerCreateInput {
  258. diskIndex := 0
  259. openclawVols := []*commonapi.ContainerVolumeMount{
  260. {
  261. Disk: &commonapi.ContainerVolumeMountDisk{
  262. Index: &diskIndex,
  263. SubDirectory: "config",
  264. },
  265. Type: commonapi.CONTAINER_VOLUME_MOUNT_TYPE_DISK,
  266. MountPath: desktopConfigDir,
  267. },
  268. {
  269. Disk: &commonapi.ContainerVolumeMountDisk{
  270. Index: &diskIndex,
  271. SubDirectory: "home",
  272. },
  273. Type: commonapi.CONTAINER_VOLUME_MOUNT_TYPE_DISK,
  274. MountPath: homeDir,
  275. },
  276. {
  277. Disk: &commonapi.ContainerVolumeMountDisk{
  278. Index: &diskIndex,
  279. SubDirectory: "data",
  280. },
  281. Type: commonapi.CONTAINER_VOLUME_MOUNT_TYPE_DISK,
  282. MountPath: openclawDataDir,
  283. },
  284. {
  285. Type: commonapi.CONTAINER_VOLUME_MOUNT_TYPE_TEXT,
  286. Text: &commonapi.ContainerVolumeMountText{
  287. Content: jsonutils.Marshal(c.getOpenClawBaseConfig(llm)).PrettyString(),
  288. },
  289. MountPath: api.LLM_OPENCLAW_CUSTOM_CONFIG_FILE,
  290. },
  291. }
  292. httpAuthUsername := "admin"
  293. httpAuthPassword := openclawFixed9DigitPassword(llm.GetId())
  294. openclawSpec := computeapi.ContainerSpec{
  295. ContainerSpec: commonapi.ContainerSpec{
  296. Image: image.ToContainerImage(),
  297. ImageCredentialId: image.CredentialId,
  298. EnableLxcfs: true,
  299. AlwaysRestart: true,
  300. ShmSizeMB: 2048,
  301. DisableNoNewPrivs: true,
  302. Envs: []*commonapi.ContainerKeyValue{
  303. // Desktop env
  304. // {Key: "TZ", Value: "Etc/UTC"},
  305. {Key: "TZ", Value: "Asia/Shanghai"},
  306. {Key: "PUID", Value: "1000"},
  307. {Key: "PGID", Value: "1000"},
  308. {Key: "LC_ALL", Value: "zh_CN.UTF-8"},
  309. // webtop envs: https://github.com/linuxserver/docker-webtop?tab=readme-ov-file#advanced-configuration
  310. // {Key: "DISABLE_SUDO", Value: "true"},
  311. // Provider
  312. // {Key: "MOONSHOT_API_KEY", Value: "abc"},
  313. // {Key: "OPENCLAW_PRIMARY_MODEL", Value: "moonshot/kimi-k2.5"},
  314. // Auth
  315. {Key: string(api.LLM_OPENCLAW_AUTH_USERNAME), Value: httpAuthUsername},
  316. {Key: string(api.LLM_OPENCLAW_CUSTOM_USER), Value: httpAuthUsername},
  317. {Key: string(api.LLM_OPENCLAW_AUTH_PASSWORD), Value: httpAuthPassword},
  318. {Key: string(api.LLM_OPENCLAW_PASSWORD), Value: httpAuthPassword},
  319. {Key: string(api.LLM_OPENCLAW_CUSTOM_CONFIG), Value: api.LLM_OPENCLAW_CUSTOM_CONFIG_FILE},
  320. // // Browser sidecar
  321. // {Key: "BROWSER_CDP_URL", Value: "http://localhost" + ":" + openclawBrowserCDPPort},
  322. // {Key: "BROWSER_DEFAULT_PROFILE", Value: "openclaw"},
  323. // {Key: "BROWSER_EVALUATE_ENABLED", Value: "true"},
  324. // OpenClaw env
  325. {Key: "OPENCLAW_GATEWAY_TOKEN", Value: llm.GetId()},
  326. {Key: "OPENCLAW_GATEWAY_PORT", Value: "18789"},
  327. {Key: "OPENCLAW_GATEWAY_BIND", Value: "loopback"},
  328. {Key: "OPENCLAW_STATE_DIR", Value: "/config/.openclaw"},
  329. {Key: "OPENCLAW_WORKSPACE_DIR", Value: "/config/.openclaw/workspace"},
  330. // Brew env
  331. {Key: "HOMEBREW_PREFIX", Value: "/home/linuxbrew/.linuxbrew"},
  332. {Key: "HOMEBREW_CELLAR", Value: "/home/linuxbrew/.linuxbrew/Cellar"},
  333. {Key: "HOMEBREW_REPOSITORY", Value: "/home/linuxbrew/.linuxbrew/Homebrew"},
  334. // Selkies env
  335. {Key: "SELKIES_UI_TITLE", Value: "Cloudpods Desktop"},
  336. {Key: "SELKIES_UI_SHOW_LOGO", Value: "False"},
  337. {Key: "SELKIES_UI_SIDEBAR_SHOW_APPS", Value: "False"},
  338. {Key: "SELKIES_UI_SIDEBAR_SHOW_GAMEPADS", Value: "False"},
  339. },
  340. },
  341. VolumeMounts: openclawVols,
  342. RootFs: &commonapi.ContainerRootfs{
  343. Type: commonapi.CONTAINER_VOLUME_MOUNT_TYPE_DISK,
  344. Disk: &commonapi.ContainerVolumeMountDisk{
  345. Index: &diskIndex,
  346. SubDirectory: "rootfs",
  347. },
  348. Persistent: false,
  349. },
  350. }
  351. // inject credential envs
  352. // spec := c.GetEffectiveSpec(llm, sku)
  353. if llm.LLMSpec == nil || llm.LLMSpec.OpenClaw == nil {
  354. return []*computeapi.PodContainerCreateInput{
  355. {
  356. Name: fmt.Sprintf("%s-%d", llm.GetName(), 0),
  357. ContainerSpec: openclawSpec,
  358. },
  359. }
  360. }
  361. spec := llm.LLMSpec.OpenClaw
  362. for _, provider := range spec.Providers {
  363. openclawSpec.Envs = appendCredentialEnvs(openclawSpec.Envs, provider.Credential)
  364. }
  365. for _, channel := range spec.Channels {
  366. openclawSpec.Envs = appendCredentialEnvs(openclawSpec.Envs, channel.Credential)
  367. }
  368. if sku.LLMSpec != nil && sku.LLMSpec.OpenClaw != nil {
  369. skuSpec := sku.LLMSpec.OpenClaw
  370. // inject workspace templates
  371. if skuSpec.WorkspaceTemplates != nil {
  372. if skuSpec.WorkspaceTemplates.AgentsMD != "" {
  373. openclawSpec.Envs = append(openclawSpec.Envs, &commonapi.ContainerKeyValue{
  374. Key: string(api.LLM_OPENCLAW_TEMPLATE_AGENTS_MD_B64),
  375. Value: base64.StdEncoding.EncodeToString([]byte(skuSpec.WorkspaceTemplates.AgentsMD)),
  376. })
  377. }
  378. if skuSpec.WorkspaceTemplates.SoulMD != "" {
  379. openclawSpec.Envs = append(openclawSpec.Envs, &commonapi.ContainerKeyValue{
  380. Key: string(api.LLM_OPENCLAW_TEMPLATE_SOUL_MD_B64),
  381. Value: base64.StdEncoding.EncodeToString([]byte(skuSpec.WorkspaceTemplates.SoulMD)),
  382. })
  383. }
  384. if skuSpec.WorkspaceTemplates.UserMD != "" {
  385. openclawSpec.Envs = append(openclawSpec.Envs, &commonapi.ContainerKeyValue{
  386. Key: string(api.LLM_OPENCLAW_TEMPLATE_USER_MD_B64),
  387. Value: base64.StdEncoding.EncodeToString([]byte(skuSpec.WorkspaceTemplates.UserMD)),
  388. })
  389. }
  390. }
  391. }
  392. return []*computeapi.PodContainerCreateInput{
  393. {
  394. Name: fmt.Sprintf("%s-%d", llm.GetName(), 0),
  395. ContainerSpec: openclawSpec,
  396. },
  397. }
  398. }
  399. func (c *openclaw) GetLLMAccessUrlInfo(ctx context.Context, userCred mcclient.TokenCredential, llm *models.SLLM, input *models.LLMAccessInfoInput) (*api.LLMAccessUrlInfo, error) {
  400. return models.GetLLMAccessUrlInfo(ctx, userCred, llm, input, "https", api.LLM_OPENCLAW_DEFAULT_PORT)
  401. }
  402. // GetLoginInfo returns OpenClaw web UI login credentials (same defaults as container env).
  403. func (c *openclaw) GetLoginInfo(ctx context.Context, userCred mcclient.TokenCredential, llm *models.SLLM) (*api.LLMAccessInfo, error) {
  404. ctr, err := llm.GetLLMSContainer(ctx)
  405. if err != nil {
  406. if errors.Cause(err) == sql.ErrNoRows || strings.Contains(strings.ToLower(err.Error()), "not found") {
  407. return nil, nil
  408. }
  409. return nil, errors.Wrap(err, "get llm cloud container")
  410. }
  411. if ctr.Spec == nil {
  412. return nil, errors.Wrap(errors.ErrEmpty, "no Spec")
  413. }
  414. var (
  415. username string
  416. password string
  417. gatewayToken string
  418. )
  419. for _, env := range ctr.Spec.Envs {
  420. if env.Key == string(api.LLM_OPENCLAW_AUTH_USERNAME) {
  421. username = env.Value
  422. }
  423. if env.Key == string(api.LLM_OPENCLAW_AUTH_PASSWORD) {
  424. password = env.Value
  425. }
  426. if env.Key == string(api.LLM_OPENCLAW_GATEWAY_TOKEN) {
  427. gatewayToken = env.Value
  428. }
  429. }
  430. return &api.LLMAccessInfo{
  431. Username: username,
  432. Password: password,
  433. Extra: map[string]string{
  434. string(api.LLM_OPENCLAW_GATEWAY_TOKEN): gatewayToken,
  435. },
  436. }, nil
  437. }
  438. func (c *openclaw) GetProbedInstantModelsExt(ctx context.Context, userCred mcclient.TokenCredential, llm *models.SLLM, mdlIds ...string) (map[string]api.LLMInternalInstantMdlInfo, error) {
  439. return nil, nil
  440. }
  441. func (c *openclaw) DetectModelPaths(ctx context.Context, userCred mcclient.TokenCredential, llm *models.SLLM, pkgInfo api.LLMInternalInstantMdlInfo) ([]string, error) {
  442. return nil, nil
  443. }
  444. func (c *openclaw) GetImageInternalPathMounts(sApp *models.SInstantModel) map[string]string {
  445. return nil
  446. }
  447. func (c *openclaw) GetSaveDirectories(sApp *models.SInstantModel) (string, []string, error) {
  448. return "", nil, nil
  449. }
  450. func (c *openclaw) ValidateMounts(mounts []string, mdlName string, mdlTag string) ([]string, error) {
  451. return nil, nil
  452. }
  453. func (c *openclaw) CheckDuplicateMounts(errStr string, dupIndex int) string {
  454. return "Duplicate mounts detected"
  455. }
  456. func (c *openclaw) GetInstantModelIdByPostOverlay(postOverlay *commonapi.ContainerVolumeMountDiskPostOverlay, mdlNameToId map[string]string) string {
  457. return ""
  458. }
  459. func (c *openclaw) GetDirPostOverlay(dir api.LLMMountDirInfo) *commonapi.ContainerVolumeMountDiskPostOverlay {
  460. return nil
  461. }
  462. func (c *openclaw) PreInstallModel(ctx context.Context, userCred mcclient.TokenCredential, llm *models.SLLM, instMdl *models.SLLMInstantModel) error {
  463. return nil
  464. }
  465. func (c *openclaw) InstallModel(ctx context.Context, userCred mcclient.TokenCredential, llm *models.SLLM, dirs []string, mdlIds []string) error {
  466. return nil
  467. }
  468. func (c *openclaw) UninstallModel(ctx context.Context, userCred mcclient.TokenCredential, llm *models.SLLM, instMdl *models.SLLMInstantModel) error {
  469. return nil
  470. }
  471. func (c *openclaw) DownloadModel(ctx context.Context, userCred mcclient.TokenCredential, llm *models.SLLM, tmpDir string, modelName string, modelTag string) (string, []string, error) {
  472. return "", nil, nil
  473. }