package geminiweb import ( "context" "cursor-api-proxy/internal/apitypes" "cursor-api-proxy/internal/config" "fmt" "os" "os/signal" "syscall" "time" "github.com/go-rod/rod" ) type Provider struct { cfg config.BridgeConfig pool *SessionPool } func NewProvider(cfg config.BridgeConfig) *Provider { return &Provider{cfg: cfg} } func (p *Provider) Name() string { return "gemini-web" } func (p *Provider) Close() error { return nil } func (p *Provider) initPool() error { if p.pool != nil { return nil } pool, err := NewSessionPool(p.cfg.GeminiAccountDir, p.cfg.GeminiMaxSessions) if err != nil { return fmt.Errorf("failed to init session pool: %w", err) } p.pool = pool return nil } func (p *Provider) Generate(ctx context.Context, model string, messages []apitypes.Message, tools []apitypes.Tool, cb func(apitypes.StreamChunk)) error { if err := p.initPool(); err != nil { return err } session := p.pool.GetAvailable() if session == nil { return fmt.Errorf("no available sessions") } p.pool.StartSession(session) defer p.pool.EndSession(session) browser, err := NewBrowser(p.cfg.GeminiBrowserVisible) if err != nil { return fmt.Errorf("failed to create browser: %w", err) } defer browser.Close() page, err := browser.NewPage() if err != nil { return fmt.Errorf("failed to create page: %w", err) } if session.CookieFile != "" { cookies, err := LoadCookiesFromFile(session.CookieFile) if err == nil { if err := SetCookiesOnPage(page, cookies); err != nil { return fmt.Errorf("failed to set cookies: %w", err) } } } if err := NavigateToGemini(page); err != nil { return fmt.Errorf("failed to navigate: %w", err) } time.Sleep(2 * time.Second) if !IsLoggedIn(page) { return fmt.Errorf("session not logged in, please run gemini-login first") } if err := SelectModel(page, model); err != nil { return fmt.Errorf("failed to select model: %w", err) } time.Sleep(500 * time.Millisecond) prompt := buildPromptFromMessages(messages) if err := SendPrompt(page, prompt); err != nil { return fmt.Errorf("failed to send prompt: %w", err) } return WaitForResponse(page, func(text string) { cb(apitypes.StreamChunk{Type: apitypes.ChunkText, Text: text}) }, func(thinking string) { cb(apitypes.StreamChunk{Type: apitypes.ChunkThinking, Thinking: thinking}) }, func() { cb(apitypes.StreamChunk{Type: apitypes.ChunkDone, Done: true}) }, ) } func buildPromptFromMessages(messages []apitypes.Message) string { var prompt string for _, m := range messages { switch m.Role { case "system": prompt += "System: " + m.Content + "\n\n" case "user": prompt += m.Content + "\n\n" case "assistant": prompt += "Assistant: " + m.Content + "\n\n" } } return prompt } func RunLogin(cfg config.BridgeConfig, sessionName string) error { if sessionName == "" { sessionName = fmt.Sprintf("session-%d", time.Now().Unix()) } pool, err := NewSessionPool(cfg.GeminiAccountDir, cfg.GeminiMaxSessions) if err != nil { return fmt.Errorf("failed to init pool: %w", err) } session, err := pool.CreateSession(sessionName) if err != nil { return fmt.Errorf("failed to create session: %w", err) } fmt.Printf("Starting browser for login. Session: %s\n", sessionName) fmt.Println("Please log in to your Gemini account in the browser window.") fmt.Println("Press Ctrl+C when you have completed the login...") browser, err := NewBrowser(true) if err != nil { return fmt.Errorf("failed to create browser: %w", err) } defer browser.Close() page, err := browser.NewPage() if err != nil { return fmt.Errorf("failed to create page: %w", err) } if err := NavigateToGemini(page); err != nil { return fmt.Errorf("failed to navigate: %w", err) } sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) <-sigChan cookies, err := GetPageCookies(page) if err != nil { return fmt.Errorf("failed to get cookies: %w", err) } if err := SaveCookiesToFile(cookies, session.CookieFile); err != nil { return fmt.Errorf("failed to save cookies: %w", err) } fmt.Printf("Session saved successfully: %s\n", sessionName) return nil } func GetPageCookies(page *rod.Page) ([]Cookie, error) { cookies, err := page.Cookies([]string{}) if err != nil { return nil, fmt.Errorf("failed to get cookies: %w", err) } var result []Cookie for _, c := range cookies { result = append(result, Cookie{ Name: c.Name, Value: c.Value, Domain: c.Domain, Path: c.Path, HTTPOnly: c.HTTPOnly, Secure: c.Secure, }) } return result, nil }