package client import ( "bytes" "context" "encoding/json" "fmt" "io" "mime/multipart" "net/http" "os" "path/filepath" "time" ) type Client struct { baseURL string httpClient *http.Client } func New(baseURL string) *Client { return &Client{ baseURL: baseURL, httpClient: &http.Client{Timeout: 30 * time.Second}, } } type SessionInfo struct { Sessions []string `json:"sessions"` } func (c *Client) ListSessions(ctx context.Context, user string) ([]string, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/sessions/%s", c.baseURL, user), nil) if err != nil { return nil, err } resp, err := c.httpClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("list sessions failed: %s", resp.Status) } var data SessionInfo if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { return nil, err } return data.Sessions, nil } type ChatRequest struct { User string `json:"user"` Session string `json:"session"` Prompt string `json:"prompt"` } func (c *Client) ChatStream(ctx context.Context, user, session, prompt string) (io.ReadCloser, error) { body, err := json.Marshal(ChatRequest{User: user, Session: session, Prompt: prompt}) if err != nil { return nil, err } req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/chat/stream", bytes.NewReader(body)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") resp, err := c.httpClient.Do(req) if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { defer resp.Body.Close() b, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("chat failed: %s - %s", resp.Status, string(b)) } return resp.Body, nil } type UploadResp struct { Path string `json:"path"` } func (c *Client) UploadDocument(ctx context.Context, user, session, path string) (string, error) { file, err := os.Open(path) if err != nil { return "", err } defer file.Close() buf := &bytes.Buffer{} writer := multipart.NewWriter(buf) _ = writer.WriteField("user", user) _ = writer.WriteField("session", session) fw, err := writer.CreateFormFile("file", filepath.Base(path)) if err != nil { return "", err } if _, err = io.Copy(fw, file); err != nil { return "", err } writer.Close() req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/upload", buf) if err != nil { return "", err } req.Header.Set("Content-Type", writer.FormDataContentType()) resp, err := c.httpClient.Do(req) if err != nil { return "", err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { b, _ := io.ReadAll(resp.Body) return "", fmt.Errorf("upload failed: %s - %s", resp.Status, string(b)) } var out UploadResp if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { return "", err } return out.Path, nil } type DirEntry struct { Name string `json:"name"` IsDir bool `json:"is_dir"` } type DirList struct { Entries []DirEntry `json:"entries"` } func (c *Client) ListDir(ctx context.Context, user, path string) ([]DirEntry, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/vm/%s/list?path=%s", c.baseURL, user, path), nil) if err != nil { return nil, err } resp, err := c.httpClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("list dir failed: %s", resp.Status) } var out DirList if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { return nil, err } return out.Entries, nil } type FileContent struct { Content string `json:"content"` } func (c *Client) ReadFile(ctx context.Context, user, path string) (string, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/vm/%s/file?path=%s", c.baseURL, user, path), nil) if err != nil { return "", err } resp, err := c.httpClient.Do(req) if err != nil { return "", err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("read file failed: %s", resp.Status) } var out FileContent if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { return "", err } return out.Content, nil } func (c *Client) WriteFile(ctx context.Context, user, path, content string) error { data, _ := json.Marshal(map[string]string{"path": path, "content": content}) req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("%s/vm/%s/file", c.baseURL, user), bytes.NewReader(data)) if err != nil { return err } req.Header.Set("Content-Type", "application/json") resp, err := c.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { b, _ := io.ReadAll(resp.Body) return fmt.Errorf("write file failed: %s - %s", resp.Status, string(b)) } return nil } func (c *Client) DeleteFile(ctx context.Context, user, path string) error { req, err := http.NewRequestWithContext(ctx, http.MethodDelete, fmt.Sprintf("%s/vm/%s/file?path=%s", c.baseURL, user, path), nil) if err != nil { return err } resp, err := c.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { b, _ := io.ReadAll(resp.Body) return fmt.Errorf("delete file failed: %s - %s", resp.Status, string(b)) } return nil }