Akash Yadav commited on
Commit
e61cceb
·
1 Parent(s): da8a1c1

Add OpenAI-compatible endpoint to Gemini server (/v1/chat/completions with streaming)

Browse files
Files changed (1) hide show
  1. free-gemini-api/main.go +167 -0
free-gemini-api/main.go CHANGED
@@ -18,9 +18,54 @@ import (
18
 
19
  "github.com/gofiber/fiber/v3"
20
  "github.com/gofiber/fiber/v3/middleware/logger"
 
21
  "github.com/joho/godotenv"
22
  )
23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  var (
25
  userSessions sync.Map
26
  )
@@ -334,6 +379,128 @@ func main() {
334
  return c.JSON(resp)
335
  })
336
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
  // Music generation endpoint (separate because it uses tool="music_gen")
338
  app.Post("/music", func(c fiber.Ctx) error {
339
  var req gemini.ChatRequest
 
18
 
19
  "github.com/gofiber/fiber/v3"
20
  "github.com/gofiber/fiber/v3/middleware/logger"
21
+ "github.com/google/uuid"
22
  "github.com/joho/godotenv"
23
  )
24
 
25
+ type OpenAIChatMessage struct {
26
+ Role string `json:"role"`
27
+ Content string `json:"content"`
28
+ }
29
+
30
+ type OpenAIChatCompletionRequest struct {
31
+ Model string `json:"model"`
32
+ Messages []OpenAIChatMessage `json:"messages"`
33
+ Stream bool `json:"stream"`
34
+ }
35
+
36
+ type OpenAIChoice struct {
37
+ Index int `json:"index"`
38
+ Message OpenAIChatMessage `json:"message"`
39
+ FinishReason string `json:"finish_reason"`
40
+ }
41
+
42
+ type OpenAIChatCompletionResponse struct {
43
+ ID string `json:"id"`
44
+ Object string `json:"object"`
45
+ Created int64 `json:"created"`
46
+ Model string `json:"model"`
47
+ Choices []OpenAIChoice `json:"choices"`
48
+ }
49
+
50
+ type OpenAIDelta struct {
51
+ Role string `json:"role,omitempty"`
52
+ Content string `json:"content,omitempty"`
53
+ }
54
+
55
+ type OpenAIStreamChoice struct {
56
+ Index int `json:"index"`
57
+ Delta OpenAIDelta `json:"delta"`
58
+ FinishReason string `json:"finish_reason,omitempty"`
59
+ }
60
+
61
+ type OpenAIChatCompletionChunk struct {
62
+ ID string `json:"id"`
63
+ Object string `json:"object"`
64
+ Created int64 `json:"created"`
65
+ Model string `json:"model"`
66
+ Choices []OpenAIStreamChoice `json:"choices"`
67
+ }
68
+
69
  var (
70
  userSessions sync.Map
71
  )
 
379
  return c.JSON(resp)
380
  })
381
 
382
+ // OpenAI-compatible endpoint for drop-in client integrations
383
+ app.Post("/v1/chat/completions", func(c fiber.Ctx) error {
384
+ var req OpenAIChatCompletionRequest
385
+ if err := c.Bind().JSON(&req); err != nil {
386
+ return c.Status(400).JSON(fiber.Map{"error": "Invalid request"})
387
+ }
388
+
389
+ if len(req.Messages) == 0 {
390
+ return c.Status(400).JSON(fiber.Map{"error": "Messages array is empty"})
391
+ }
392
+
393
+ // Extract prompt from messages (last user message)
394
+ var prompt string
395
+ for i := len(req.Messages) - 1; i >= 0; i-- {
396
+ if req.Messages[i].Role == "user" {
397
+ prompt = req.Messages[i].Content
398
+ break
399
+ }
400
+ }
401
+ if prompt == "" {
402
+ prompt = req.Messages[len(req.Messages)-1].Content
403
+ }
404
+
405
+ sessionID := c.IP()
406
+
407
+ client, err := getOrCreateClient(sessionID)
408
+ if err != nil {
409
+ return c.Status(500).JSON(fiber.Map{"error": err.Error()})
410
+ }
411
+
412
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
413
+ defer cancel()
414
+
415
+ model := req.Model
416
+ if model == "" {
417
+ model = "gemini-2.5-flash"
418
+ }
419
+
420
+ if req.Stream {
421
+ c.Set("Content-Type", "text/event-stream")
422
+ c.Set("Cache-Control", "no-cache")
423
+ c.Set("Connection", "keep-alive")
424
+ c.Set("X-Accel-Buffering", "no")
425
+
426
+ return c.SendStreamWriter(func(w *bufio.Writer) {
427
+ chunkID := "chatcmpl-" + uuid.NewString()[:12]
428
+ createdTime := time.Now().Unix()
429
+
430
+ _, streamErr := client.AskStream(prompt, func(chunk string) {
431
+ chunkResp := OpenAIChatCompletionChunk{
432
+ ID: chunkID,
433
+ Object: "chat.completion.chunk",
434
+ Created: createdTime,
435
+ Model: model,
436
+ Choices: []OpenAIStreamChoice{
437
+ {
438
+ Index: 0,
439
+ Delta: OpenAIDelta{
440
+ Content: chunk,
441
+ },
442
+ },
443
+ },
444
+ }
445
+ data, _ := json.Marshal(chunkResp)
446
+ fmt.Fprintf(w, "data: %s\n\n", data)
447
+ w.Flush()
448
+ })
449
+
450
+ if streamErr != nil {
451
+ log.Printf("❌ Streaming error: %v", streamErr)
452
+ return
453
+ }
454
+
455
+ // Send final stop chunk
456
+ finalResp := OpenAIChatCompletionChunk{
457
+ ID: chunkID,
458
+ Object: "chat.completion.chunk",
459
+ Created: createdTime,
460
+ Model: model,
461
+ Choices: []OpenAIStreamChoice{
462
+ {
463
+ Index: 0,
464
+ FinishReason: "stop",
465
+ },
466
+ },
467
+ }
468
+ data, _ := json.Marshal(finalResp)
469
+ fmt.Fprintf(w, "data: %s\n\n", data)
470
+ fmt.Fprintf(w, "data: [DONE]\n\n")
471
+ w.Flush()
472
+ })
473
+ }
474
+
475
+ resp, err := client.Ask(prompt)
476
+ if err != nil {
477
+ if ctx.Err() == context.DeadlineExceeded {
478
+ userSessions.Delete(sessionID)
479
+ return c.Status(504).JSON(fiber.Map{"error": "Request timed out. Session reset."})
480
+ }
481
+ return c.Status(500).JSON(fiber.Map{"error": err.Error()})
482
+ }
483
+
484
+ openAIResp := OpenAIChatCompletionResponse{
485
+ ID: "chatcmpl-" + uuid.NewString()[:12],
486
+ Object: "chat.completion",
487
+ Created: time.Now().Unix(),
488
+ Model: model,
489
+ Choices: []OpenAIChoice{
490
+ {
491
+ Index: 0,
492
+ Message: OpenAIChatMessage{
493
+ Role: "assistant",
494
+ Content: resp.Text,
495
+ },
496
+ FinishReason: "stop",
497
+ },
498
+ },
499
+ }
500
+
501
+ return c.JSON(openAIResp)
502
+ })
503
+
504
  // Music generation endpoint (separate because it uses tool="music_gen")
505
  app.Post("/music", func(c fiber.Ctx) error {
506
  var req gemini.ChatRequest