Files
groqqer/groqqer.go
T

2252 lines
64 KiB
Go
Raw Normal View History

// groqqer: OpenAI-compatible LLM gateway for the Groq Streamlit space
// Reverse engineers https://dromerosm-groq-chatbot.hf.space into an OpenAI proxy
// Created by Luxferre in 2026, released into the public domain
package main
import (
"bufio"
"context"
"crypto/rand"
"encoding/base64"
"encoding/binary"
"encoding/json"
"flag"
"fmt"
"io"
"log"
"net"
"net/http"
"os"
"os/exec"
"os/signal"
"regexp"
"strconv"
"strings"
"sync"
"sync/atomic"
"syscall"
"time"
)
var (
DefaultUserAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
DefaultTargetURL = "https://dromerosm-groq-chatbot.hf.space"
DefaultModel = "llama-3.3-70b-versatile"
cdpCmdCounter int64
)
// ---------------------------------------------------------------------------
// OpenAI API Data Structures
// ---------------------------------------------------------------------------
type ModelItem struct {
ID string `json:"id"`
Object string `json:"object"`
Created int64 `json:"created"`
OwnedBy string `json:"owned_by"`
}
type ModelsResponse struct {
Object string `json:"object"`
Data []ModelItem `json:"data"`
}
type ToolCallFunction struct {
Name string `json:"name"`
Arguments string `json:"arguments"`
}
type ToolCall struct {
Index *int `json:"index,omitempty"`
ID string `json:"id,omitempty"`
Type string `json:"type,omitempty"`
Function ToolCallFunction `json:"function"`
}
type Tool struct {
Type string `json:"type"`
Function interface{} `json:"function"`
}
type ChatMessage struct {
Role string `json:"role"`
Content interface{} `json:"content"`
ReasoningContent string `json:"reasoning_content,omitempty"`
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
ToolCallID string `json:"tool_call_id,omitempty"`
Name string `json:"name,omitempty"`
}
func (m *ChatMessage) GetContentString() string {
if m.Content == nil {
return ""
}
if str, ok := m.Content.(string); ok {
return str
}
if parts, ok := m.Content.([]interface{}); ok {
var sb strings.Builder
for _, p := range parts {
if str, ok := p.(string); ok {
sb.WriteString(str)
} else if itemMap, ok := p.(map[string]interface{}); ok {
if textVal, ok := itemMap["text"].(string); ok {
sb.WriteString(textVal)
}
}
}
return sb.String()
}
b, err := json.Marshal(m.Content)
if err == nil {
return string(b)
}
return fmt.Sprintf("%v", m.Content)
}
type ChatCompletionRequest struct {
Model string `json:"model"`
Messages []ChatMessage `json:"messages"`
Tools []Tool `json:"tools,omitempty"`
ToolChoice interface{} `json:"tool_choice,omitempty"`
Stream bool `json:"stream"`
MaxTokens int `json:"max_tokens"`
MaxCompletionTokens int `json:"max_completion_tokens"`
ReasoningEffort string `json:"reasoning_effort,omitempty"`
}
type ChatCompletionResponseChoice struct {
Index int `json:"index"`
Message ChatMessage `json:"message"`
FinishReason string `json:"finish_reason"`
}
type Usage struct {
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
TotalTokens int `json:"total_tokens"`
}
type ChatCompletionResponse struct {
ID string `json:"id"`
Object string `json:"object"`
Created int64 `json:"created"`
Model string `json:"model"`
Choices []ChatCompletionResponseChoice `json:"choices"`
Usage Usage `json:"usage"`
}
type StreamDelta struct {
Role string `json:"role,omitempty"`
Content string `json:"content,omitempty"`
ReasoningContent string `json:"reasoning_content,omitempty"`
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
}
type StreamChoice struct {
Index int `json:"index"`
Delta StreamDelta `json:"delta"`
FinishReason string `json:"finish_reason,omitempty"`
}
type StreamResponse struct {
ID string `json:"id"`
Object string `json:"object"`
Created int64 `json:"created"`
Model string `json:"model"`
Choices []StreamChoice `json:"choices"`
}
// ---------------------------------------------------------------------------
// UUID & Utility Functions
// ---------------------------------------------------------------------------
func GenerateUUID() string {
var b [16]byte
_, _ = rand.Read(b[:])
b[6] = (b[6] & 0x0f) | 0x40
b[8] = (b[8] & 0x3f) | 0x80
return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16])
}
func getFreePort() (string, error) {
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return "", err
}
defer listener.Close()
return strconv.Itoa(listener.Addr().(*net.TCPAddr).Port), nil
}
func findFreeXDisplay() string {
for d := 100; d < 200; d++ {
lockFile := fmt.Sprintf("/tmp/.X%d-lock", d)
sockFile := fmt.Sprintf("/tmp/.X11-unix/X%d", d)
if _, err := os.Stat(lockFile); os.IsNotExist(err) {
if _, err2 := os.Stat(sockFile); os.IsNotExist(err2) {
return fmt.Sprintf(":%d", d)
}
}
}
return ":100"
}
// ---------------------------------------------------------------------------
// SOCKS5 Proxy Client (RFC 1928 / RFC 1929)
// ---------------------------------------------------------------------------
type SOCKS5Config struct {
Address string
Username string
Password string
}
func ParseSOCKS5URL(proxyURL string) (*SOCKS5Config, error) {
cleanURL := strings.TrimSpace(proxyURL)
if cleanURL == "" {
return nil, nil
}
if strings.HasPrefix(cleanURL, "socks5://") {
cleanURL = strings.TrimPrefix(cleanURL, "socks5://")
} else if strings.HasPrefix(cleanURL, "socks5h://") {
cleanURL = strings.TrimPrefix(cleanURL, "socks5h://")
}
cfg := &SOCKS5Config{}
if atIdx := strings.LastIndex(cleanURL, "@"); atIdx != -1 {
userPass := cleanURL[:atIdx]
cfg.Address = cleanURL[atIdx+1:]
if colonIdx := strings.Index(userPass, ":"); colonIdx != -1 {
cfg.Username = userPass[:colonIdx]
cfg.Password = userPass[colonIdx+1:]
} else {
cfg.Username = userPass
}
} else {
cfg.Address = cleanURL
}
if !strings.Contains(cfg.Address, ":") {
cfg.Address = cfg.Address + ":1080"
}
return cfg, nil
}
func DialSOCKS5(ctx context.Context, proxyURL, targetAddr string) (net.Conn, error) {
cfg, err := ParseSOCKS5URL(proxyURL)
if err != nil {
return nil, fmt.Errorf("invalid socks5 proxy configuration: %w", err)
}
if cfg == nil {
var d net.Dialer
return d.DialContext(ctx, "tcp", targetAddr)
}
var d net.Dialer
conn, err := d.DialContext(ctx, "tcp", cfg.Address)
if err != nil {
return nil, fmt.Errorf("failed to connect to socks5 proxy at %s: %w", cfg.Address, err)
}
deadline, ok := ctx.Deadline()
if !ok {
deadline = time.Now().Add(30 * time.Second)
}
conn.SetDeadline(deadline)
defer conn.SetDeadline(time.Time{})
var greeting []byte
if cfg.Username != "" {
greeting = []byte{0x05, 0x02, 0x00, 0x02}
} else {
greeting = []byte{0x05, 0x01, 0x00}
}
if _, err := conn.Write(greeting); err != nil {
conn.Close()
return nil, fmt.Errorf("failed to write socks5 greeting: %w", err)
}
resp := make([]byte, 2)
if _, err := io.ReadFull(conn, resp); err != nil {
conn.Close()
return nil, fmt.Errorf("failed to read socks5 greeting response: %w", err)
}
if resp[0] != 0x05 {
conn.Close()
return nil, fmt.Errorf("unsupported socks version: 0x%02x", resp[0])
}
if resp[1] == 0x02 {
if cfg.Username == "" {
conn.Close()
return nil, fmt.Errorf("socks5 proxy requires authentication, but no credentials provided")
}
uLen := byte(len(cfg.Username))
pLen := byte(len(cfg.Password))
authReq := []byte{0x01, uLen}
authReq = append(authReq, []byte(cfg.Username)...)
authReq = append(authReq, pLen)
authReq = append(authReq, []byte(cfg.Password)...)
if _, err := conn.Write(authReq); err != nil {
conn.Close()
return nil, fmt.Errorf("failed to send socks5 authentication: %w", err)
}
authResp := make([]byte, 2)
if _, err := io.ReadFull(conn, authResp); err != nil {
conn.Close()
return nil, fmt.Errorf("failed to read socks5 auth response: %w", err)
}
if authResp[1] != 0x00 {
conn.Close()
return nil, fmt.Errorf("socks5 authentication failed with status 0x%02x", authResp[1])
}
} else if resp[1] != 0x00 {
conn.Close()
return nil, fmt.Errorf("socks5 proxy rejected authentication methods: 0x%02x", resp[1])
}
host, portStr, err := net.SplitHostPort(targetAddr)
if err != nil {
conn.Close()
return nil, fmt.Errorf("invalid target address %s: %w", targetAddr, err)
}
port, err := strconv.Atoi(portStr)
if err != nil || port < 1 || port > 65535 {
conn.Close()
return nil, fmt.Errorf("invalid port in target address %s", targetAddr)
}
reqBuf := []byte{0x05, 0x01, 0x00}
ip := net.ParseIP(host)
if ip4 := ip.To4(); ip4 != nil {
reqBuf = append(reqBuf, 0x01)
reqBuf = append(reqBuf, ip4...)
} else if ip6 := ip.To16(); ip6 != nil {
reqBuf = append(reqBuf, 0x04)
reqBuf = append(reqBuf, ip6...)
} else {
reqBuf = append(reqBuf, 0x03, byte(len(host)))
reqBuf = append(reqBuf, []byte(host)...)
}
reqBuf = append(reqBuf, byte(port>>8), byte(port&0xFF))
if _, err := conn.Write(reqBuf); err != nil {
conn.Close()
return nil, fmt.Errorf("failed to send socks5 connect request: %w", err)
}
respHdr := make([]byte, 4)
if _, err := io.ReadFull(conn, respHdr); err != nil {
conn.Close()
return nil, fmt.Errorf("failed to read socks5 connect response: %w", err)
}
if respHdr[1] != 0x00 {
conn.Close()
return nil, fmt.Errorf("socks5 connect failed with reply code 0x%02x", respHdr[1])
}
switch respHdr[3] {
case 0x01:
bnd := make([]byte, 6)
io.ReadFull(conn, bnd)
case 0x03:
lenBuf := make([]byte, 1)
io.ReadFull(conn, lenBuf)
bnd := make([]byte, int(lenBuf[0])+2)
io.ReadFull(conn, bnd)
case 0x04:
bnd := make([]byte, 18)
io.ReadFull(conn, bnd)
}
return conn, nil
}
// ---------------------------------------------------------------------------
// Tool and Message Processing
// ---------------------------------------------------------------------------
func BuildToolInstruction(tools []Tool, toolChoice interface{}) string {
if len(tools) == 0 {
return ""
}
toolsBytes, _ := json.Marshal(tools)
var sb strings.Builder
sb.WriteString("\n\n# Tool Calling Instructions\nYou have access to the following functions:\n<tools>\n")
sb.WriteString(string(toolsBytes))
sb.WriteString("\n</tools>\n\nWhen you need to call a function, respond ONLY with a <tool_call> block formatted exactly as follows:\n<tool_call>\n{\"name\": \"<function-name>\", \"arguments\": {<args-json-object>}}\n</tool_call>\n\nDo not include conversational filler before or after the tool call.")
if toolChoice != nil {
if choiceStr, ok := toolChoice.(string); ok {
if choiceStr == "required" {
sb.WriteString("\n\nYou MUST invoke at least one tool from the list above.")
}
} else if choiceMap, ok := toolChoice.(map[string]interface{}); ok {
if fnMap, ok := choiceMap["function"].(map[string]interface{}); ok {
if fnName, ok := fnMap["name"].(string); ok && fnName != "" {
sb.WriteString(fmt.Sprintf("\n\nYou MUST call the function %q.", fnName))
}
}
}
}
return sb.String()
}
func FormatPrompt(req ChatCompletionRequest) string {
toolInstruction := BuildToolInstruction(req.Tools, req.ToolChoice)
var systemInstructions []string
var historyTurns []string
var currentTurn string
for i, msg := range req.Messages {
contentStr := msg.GetContentString()
switch msg.Role {
case "system":
if contentStr != "" {
systemInstructions = append(systemInstructions, contentStr)
}
case "assistant":
var sb strings.Builder
if contentStr != "" {
sb.WriteString(contentStr)
}
for _, tc := range msg.ToolCalls {
if sb.Len() > 0 {
sb.WriteString("\n")
}
args := tc.Function.Arguments
if strings.TrimSpace(args) == "" {
args = "{}"
}
sb.WriteString(fmt.Sprintf("<tool_call>\n{\"name\": %q, \"arguments\": %s}\n</tool_call>", tc.Function.Name, args))
}
historyTurns = append(historyTurns, "Assistant: "+sb.String())
case "tool", "function":
toolName := msg.Name
if toolName == "" {
toolName = msg.ToolCallID
}
var contentJSON []byte
if json.Valid([]byte(contentStr)) {
contentJSON = []byte(contentStr)
} else {
contentJSON, _ = json.Marshal(contentStr)
}
turnText := fmt.Sprintf("<tool_response>\n{\"name\": %q, \"content\": %s}\n</tool_response>", toolName, string(contentJSON))
if i == len(req.Messages)-1 {
currentTurn = turnText
} else {
historyTurns = append(historyTurns, "Tool Result: "+turnText)
}
case "user":
if i == len(req.Messages)-1 {
currentTurn = contentStr
} else {
historyTurns = append(historyTurns, "User: "+contentStr)
}
}
}
var promptBuilder strings.Builder
if len(systemInstructions) > 0 || toolInstruction != "" {
promptBuilder.WriteString("[System Instructions]\n")
for _, sys := range systemInstructions {
promptBuilder.WriteString(sys)
promptBuilder.WriteString("\n")
}
if toolInstruction != "" {
promptBuilder.WriteString(strings.TrimSpace(toolInstruction))
promptBuilder.WriteString("\n")
}
promptBuilder.WriteString("\n")
}
if len(historyTurns) > 0 {
promptBuilder.WriteString("[Conversation History]\n")
for _, turn := range historyTurns {
promptBuilder.WriteString(turn)
promptBuilder.WriteString("\n")
}
promptBuilder.WriteString("\n")
}
if currentTurn != "" {
if len(historyTurns) > 0 || len(systemInstructions) > 0 {
promptBuilder.WriteString("[User]\n")
}
promptBuilder.WriteString(currentTurn)
}
return strings.TrimSpace(promptBuilder.String())
}
func cleanJSONBlock(input string) string {
s := strings.TrimSpace(input)
if strings.HasPrefix(s, "```") {
lines := strings.Split(s, "\n")
if len(lines) >= 2 {
if strings.HasPrefix(lines[len(lines)-1], "```") {
lines = lines[1 : len(lines)-1]
} else {
lines = lines[1:]
}
s = strings.TrimSpace(strings.Join(lines, "\n"))
}
}
return s
}
func sanitizeJSONValue(v interface{}) interface{} {
switch val := v.(type) {
case string:
return strings.TrimSpace(val)
case map[string]interface{}:
cleanMap := make(map[string]interface{})
for k, childV := range val {
cleanKey := strings.TrimSpace(k)
cleanMap[cleanKey] = sanitizeJSONValue(childV)
}
return cleanMap
case []interface{}:
cleanSlice := make([]interface{}, len(val))
for i, childV := range val {
cleanSlice[i] = sanitizeJSONValue(childV)
}
return cleanSlice
default:
return v
}
}
var toolNameRegex = regexp.MustCompile(`"\s*(?:name|function|action|call)\s*"\s*:\s*"\s*([^"]+?)\s*"`)
func repairToolCallJSON(jsonStr string) (ToolCall, bool) {
nameMatch := toolNameRegex.FindStringSubmatch(jsonStr)
if len(nameMatch) < 2 {
return ToolCall{}, false
}
nameVal := strings.TrimSpace(nameMatch[1])
argsStr := "{}"
argsKwList := []string{`"arguments"`, `" parameters "`, `"arguments "`, `" parameters"`, `"parameters"`, `"args"`, `"input"`}
argsIdx := -1
for _, kw := range argsKwList {
idx := strings.Index(jsonStr, kw)
if idx >= 0 {
argsIdx = idx + len(kw)
break
}
}
var targetStr string
if argsIdx >= 0 {
targetStr = strings.TrimSpace(jsonStr[argsIdx:])
if strings.HasPrefix(targetStr, ":") {
targetStr = strings.TrimSpace(targetStr[1:])
}
} else {
targetStr = jsonStr
}
if strings.HasPrefix(targetStr, "{") {
endIdx := strings.LastIndex(targetStr, "}")
if endIdx > 0 {
objCandidate := targetStr[:endIdx+1]
var testMap map[string]interface{}
if json.Unmarshal([]byte(objCandidate), &testMap) == nil {
b, _ := json.Marshal(sanitizeJSONValue(testMap))
return ToolCall{
ID: "call_" + GenerateUUID()[:8],
Type: "function",
Function: ToolCallFunction{
Name: nameVal,
Arguments: string(b),
},
}, true
}
}
} else if strings.HasPrefix(targetStr, `"`) {
endIdx := strings.LastIndex(targetStr, `"`)
if endIdx > 0 {
val := strings.TrimSpace(targetStr[1:endIdx])
b, _ := json.Marshal(val)
argsStr = string(b)
}
}
return ToolCall{
ID: "call_" + GenerateUUID()[:8],
Type: "function",
Function: ToolCallFunction{
Name: nameVal,
Arguments: argsStr,
},
}, true
}
func parseSingleToolCall(jsonStr string) (ToolCall, bool) {
cleaned := cleanJSONBlock(jsonStr)
var raw map[string]interface{}
if err := json.Unmarshal([]byte(cleaned), &raw); err == nil {
sanitizedRaw, ok := sanitizeJSONValue(raw).(map[string]interface{})
if !ok {
sanitizedRaw = raw
}
for _, wrapperKey := range []string{"function", "function_call", "tool_call"} {
if fnObj, ok := sanitizedRaw[wrapperKey].(map[string]interface{}); ok {
if nameVal, ok := fnObj["name"].(string); ok && nameVal != "" {
argsStr := "{}"
var argsVal interface{}
if a, hasA := fnObj["arguments"]; hasA {
argsVal = a
} else if p, hasP := fnObj["parameters"]; hasP {
argsVal = p
} else if args, hasArgs := fnObj["args"]; hasArgs {
argsVal = args
}
if argsVal != nil {
if s, isStr := argsVal.(string); isStr {
var innerObj interface{}
if json.Unmarshal([]byte(s), &innerObj) == nil {
b, _ := json.Marshal(sanitizeJSONValue(innerObj))
argsStr = string(b)
} else {
argsStr = strings.TrimSpace(s)
}
} else {
b, _ := json.Marshal(sanitizeJSONValue(argsVal))
argsStr = string(b)
}
}
return ToolCall{
ID: "call_" + GenerateUUID()[:8],
Type: "function",
Function: ToolCallFunction{
Name: nameVal,
Arguments: argsStr,
},
}, true
}
}
}
nameVal := ""
for _, key := range []string{"name", "function", "action", "call"} {
if n, ok := sanitizedRaw[key].(string); ok && n != "" {
nameVal = n
break
}
}
if nameVal != "" {
argsStr := "{}"
var argsVal interface{}
for _, key := range []string{"arguments", "parameters", "args", "input"} {
if a, ok := sanitizedRaw[key]; ok {
argsVal = a
break
}
}
if argsVal != nil {
if s, isStr := argsVal.(string); isStr {
var innerObj interface{}
if json.Unmarshal([]byte(s), &innerObj) == nil {
b, _ := json.Marshal(sanitizeJSONValue(innerObj))
argsStr = string(b)
} else {
argsStr = strings.TrimSpace(s)
}
} else {
b, _ := json.Marshal(sanitizeJSONValue(argsVal))
argsStr = string(b)
}
} else {
argsMap := make(map[string]interface{})
for k, v := range sanitizedRaw {
if k != "name" && k != "function" && k != "type" && k != "action" && k != "call" {
argsMap[k] = v
}
}
if len(argsMap) > 0 {
b, _ := json.Marshal(sanitizeJSONValue(argsMap))
argsStr = string(b)
}
}
return ToolCall{
ID: "call_" + GenerateUUID()[:8],
Type: "function",
Function: ToolCallFunction{
Name: nameVal,
Arguments: argsStr,
},
}, true
}
}
return repairToolCallJSON(cleaned)
}
func parseXMLToolCall(block string) (ToolCall, bool) {
inner := strings.TrimSpace(block)
if strings.HasPrefix(inner, "<tool_call>") {
inner = strings.TrimPrefix(inner, "<tool_call>")
}
if strings.HasSuffix(inner, "</tool_call>") {
inner = strings.TrimSuffix(inner, "</tool_call>")
}
inner = cleanJSONBlock(inner)
if tc, ok := parseSingleToolCall(inner); ok {
return tc, true
}
var fnName string
if strings.Contains(inner, "<name>") && strings.Contains(inner, "</name>") {
nStart := strings.Index(inner, "<name>") + len("<name>")
nEnd := strings.Index(inner, "</name>")
if nStart < nEnd {
fnName = strings.TrimSpace(inner[nStart:nEnd])
}
}
var argsStr string
if strings.Contains(inner, "<arguments>") && strings.Contains(inner, "</arguments>") {
aStart := strings.Index(inner, "<arguments>") + len("<arguments>")
aEnd := strings.Index(inner, "</arguments>")
if aStart < aEnd {
argsStr = strings.TrimSpace(inner[aStart:aEnd])
}
}
if fnName != "" {
if argsStr == "" {
argsStr = "{}"
}
return ToolCall{
ID: "call_" + GenerateUUID()[:8],
Type: "function",
Function: ToolCallFunction{
Name: fnName,
Arguments: argsStr,
},
}, true
}
return ToolCall{}, false
}
func ExtractToolCallBlocks(content string) (blocks []string, remaining string) {
s := content
remaining = content
for strings.Contains(s, "<tool_call>") {
sIdx := strings.Index(s, "<tool_call>")
rest := s[sIdx+len("<tool_call>"):]
relNextSIdx := strings.Index(rest, "<tool_call>")
var nextSIdx int
if relNextSIdx != -1 {
nextSIdx = sIdx + len("<tool_call>") + relNextSIdx
} else {
nextSIdx = -1
}
relEIdx := strings.Index(rest, "</tool_call>")
var eIdx int
if relEIdx != -1 {
eIdx = sIdx + len("<tool_call>") + relEIdx
} else {
eIdx = -1
}
var blockText string
var blockEndPos int
if eIdx != -1 && (nextSIdx == -1 || eIdx < nextSIdx) {
blockEndPos = eIdx + len("</tool_call>")
blockText = s[sIdx:blockEndPos]
s = s[blockEndPos:]
} else if nextSIdx != -1 {
blockEndPos = nextSIdx
blockText = s[sIdx:blockEndPos]
s = s[blockEndPos:]
} else {
blockText = s[sIdx:]
s = ""
}
blocks = append(blocks, blockText)
}
for strings.Contains(remaining, "<tool_call>") {
st := strings.Index(remaining, "<tool_call>")
rest := remaining[st+len("<tool_call>"):]
relNext := strings.Index(rest, "<tool_call>")
var nextSt int
if relNext != -1 {
nextSt = st + len("<tool_call>") + relNext
} else {
nextSt = -1
}
relEn := strings.Index(rest, "</tool_call>")
var en int
if relEn != -1 {
en = st + len("<tool_call>") + relEn
} else {
en = -1
}
if en != -1 && (nextSt == -1 || en < nextSt) {
remaining = strings.TrimSpace(remaining[:st] + remaining[en+len("</tool_call>"):])
} else if nextSt != -1 {
remaining = strings.TrimSpace(remaining[:st] + remaining[nextSt:])
} else {
remaining = strings.TrimSpace(remaining[:st])
}
}
return blocks, remaining
}
func DetectToolCalls(content string) ([]ToolCall, string, bool) {
blocks, remaining := ExtractToolCallBlocks(content)
var calls []ToolCall
for _, block := range blocks {
if toolCall, ok := parseXMLToolCall(block); ok {
calls = append(calls, toolCall)
}
}
if len(calls) > 0 {
return calls, remaining, true
}
if tc, ok := parseSingleToolCall(strings.TrimSpace(content)); ok {
return []ToolCall{tc}, "", true
}
return nil, content, false
}
func ExtractThinking(content string) (string, string) {
if strings.Contains(content, "<think>") && strings.Contains(content, "</think>") {
start := strings.Index(content, "<think>")
end := strings.Index(content, "</think>")
if start < end {
reasoning := content[start+len("<think>") : end]
rem := content[:start] + content[end+len("</think>"):]
rem = strings.TrimPrefix(rem, "\n\n")
rem = strings.TrimPrefix(rem, "\n")
return strings.TrimSpace(rem), strings.TrimSpace(reasoning)
}
}
return content, ""
}
// ---------------------------------------------------------------------------
// Streaming Filters & Framing
// ---------------------------------------------------------------------------
type StreamThinkingFilter struct {
inThinking bool
buf string
}
func NewStreamThinkingFilter() *StreamThinkingFilter {
return &StreamThinkingFilter{}
}
func hasPrefixOf(target string, prefixes []string) int {
for _, p := range prefixes {
if strings.HasSuffix(target, p) {
return len(p)
}
}
return 0
}
func (f *StreamThinkingFilter) Feed(chunk string, onContent func(string), onReasoning func(string)) {
f.buf += chunk
thinkStartTag := "<think>"
thinkEndTag := "</think>"
thinkStartPrefixes := []string{"<", "<t", "<th", "<thi", "<thin", "<think"}
thinkEndPrefixes := []string{"<", "</", "</t", "</th", "</thi", "</thin", "</think"}
for len(f.buf) > 0 {
if !f.inThinking {
if idx := strings.Index(f.buf, thinkStartTag); idx != -1 {
before := f.buf[:idx]
if before != "" {
onContent(before)
}
f.inThinking = true
f.buf = f.buf[idx+len(thinkStartTag):]
} else if matchLen := hasPrefixOf(f.buf, thinkStartPrefixes); matchLen > 0 {
safe := f.buf[:len(f.buf)-matchLen]
if safe != "" {
onContent(safe)
}
f.buf = f.buf[len(f.buf)-matchLen:]
break
} else {
onContent(f.buf)
f.buf = ""
break
}
} else {
if idx := strings.Index(f.buf, thinkEndTag); idx != -1 {
before := f.buf[:idx]
if before != "" {
onReasoning(before)
}
f.inThinking = false
f.buf = f.buf[idx+len(thinkEndTag):]
f.buf = strings.TrimPrefix(f.buf, "\n\n")
f.buf = strings.TrimPrefix(f.buf, "\n")
} else if matchLen := hasPrefixOf(f.buf, thinkEndPrefixes); matchLen > 0 {
safe := f.buf[:len(f.buf)-matchLen]
if safe != "" {
onReasoning(safe)
}
f.buf = f.buf[len(f.buf)-matchLen:]
break
} else {
onReasoning(f.buf)
f.buf = ""
break
}
}
}
}
func (f *StreamThinkingFilter) Flush(onContent func(string), onReasoning func(string)) {
if len(f.buf) > 0 {
if f.inThinking {
onReasoning(f.buf)
} else {
onContent(f.buf)
}
f.buf = ""
}
}
type StreamToolCallFilter struct {
inToolCall bool
buf string
toolCallBuf string
toolIndex int
emittedCall bool
}
func NewStreamToolCallFilter() *StreamToolCallFilter {
return &StreamToolCallFilter{}
}
func (f *StreamToolCallFilter) Feed(chunk string, onContent func(string), onToolCall func(ToolCall)) {
f.buf += chunk
toolStartTag := "<tool_call>"
toolEndTag := "</tool_call>"
startPrefixes := []string{"<", "<t", "<to", "<too", "<tool", "<tool_", "<tool_c", "<tool_ca", "<tool_cal", "<tool_call"}
endPrefixes := []string{"<", "</", "</t", "</to", "</too", "</tool", "</tool_", "</tool_c", "</tool_ca", "</tool_cal", "</tool_call"}
for len(f.buf) > 0 {
if !f.inToolCall {
if idx := strings.Index(f.buf, toolStartTag); idx != -1 {
before := f.buf[:idx]
if before != "" {
onContent(before)
}
f.inToolCall = true
f.buf = f.buf[idx+len(toolStartTag):]
} else if matchLen := hasPrefixOf(f.buf, startPrefixes); matchLen > 0 {
safe := f.buf[:len(f.buf)-matchLen]
if safe != "" {
onContent(safe)
}
f.buf = f.buf[len(f.buf)-matchLen:]
break
} else {
onContent(f.buf)
f.buf = ""
break
}
} else {
if idx := strings.Index(f.buf, toolEndTag); idx != -1 {
f.toolCallBuf += f.buf[:idx]
f.buf = f.buf[idx+len(toolEndTag):]
f.inToolCall = false
if tc, ok := parseSingleToolCall(f.toolCallBuf); ok {
idxCopy := f.toolIndex
tc.Index = &idxCopy
f.toolIndex++
f.emittedCall = true
onToolCall(tc)
} else if tc2, ok2 := parseXMLToolCall("<tool_call>" + f.toolCallBuf + "</tool_call>"); ok2 {
idxCopy := f.toolIndex
tc2.Index = &idxCopy
f.toolIndex++
f.emittedCall = true
onToolCall(tc2)
} else {
onContent("<tool_call>" + f.toolCallBuf + "</tool_call>")
}
f.toolCallBuf = ""
} else if matchLen := hasPrefixOf(f.buf, endPrefixes); matchLen > 0 {
safe := f.buf[:len(f.buf)-matchLen]
f.toolCallBuf += safe
f.buf = f.buf[len(f.buf)-matchLen:]
break
} else {
f.toolCallBuf += f.buf
f.buf = ""
break
}
}
}
}
func (f *StreamToolCallFilter) Flush(onContent func(string), onToolCall func(ToolCall)) {
if f.inToolCall && len(f.toolCallBuf) > 0 {
if tc, ok := parseSingleToolCall(f.toolCallBuf); ok {
idxCopy := f.toolIndex
tc.Index = &idxCopy
f.emittedCall = true
onToolCall(tc)
} else if tc2, ok2 := parseXMLToolCall("<tool_call>" + f.toolCallBuf + "</tool_call>"); ok2 {
idxCopy := f.toolIndex
tc2.Index = &idxCopy
f.emittedCall = true
onToolCall(tc2)
} else {
onContent("<tool_call>" + f.toolCallBuf)
}
f.toolCallBuf = ""
}
if len(f.buf) > 0 {
onContent(f.buf)
f.buf = ""
}
}
type Streamer struct {
w http.ResponseWriter
flusher http.Flusher
id string
created int64
model string
}
func NewStreamer(w http.ResponseWriter, flusher http.Flusher, id string, created int64, model string) *Streamer {
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
return &Streamer{w: w, flusher: flusher, id: id, created: created, model: model}
}
func (s *Streamer) Role() {
sendStreamDelta(s.w, s.flusher, s.id, s.created, s.model, StreamDelta{Role: "assistant"}, "")
}
func (s *Streamer) Reasoning(text string) {
if text != "" {
sendStreamDelta(s.w, s.flusher, s.id, s.created, s.model, StreamDelta{ReasoningContent: text}, "")
}
}
func (s *Streamer) Content(text string) {
if text != "" {
sendStreamDelta(s.w, s.flusher, s.id, s.created, s.model, StreamDelta{Content: text}, "")
}
}
func (s *Streamer) ToolCall(tc ToolCall) {
sendStreamDelta(s.w, s.flusher, s.id, s.created, s.model, StreamDelta{ToolCalls: []ToolCall{tc}}, "")
}
func (s *Streamer) Finish(reason string) {
sendStreamDelta(s.w, s.flusher, s.id, s.created, s.model, StreamDelta{}, reason)
fmt.Fprintf(s.w, "data: [DONE]\n\n")
if s.flusher != nil {
s.flusher.Flush()
}
}
func sendStreamDelta(w http.ResponseWriter, flusher http.Flusher, id string, created int64, model string, delta StreamDelta, finishReason string) {
chunk := StreamResponse{
ID: id,
Object: "chat.completion.chunk",
Created: created,
Model: model,
Choices: []StreamChoice{
{
Index: 0,
Delta: delta,
FinishReason: finishReason,
},
},
}
b, _ := json.Marshal(chunk)
fmt.Fprintf(w, "data: %s\n\n", b)
if flusher != nil {
flusher.Flush()
}
}
func WriteCompletionResponse(w http.ResponseWriter, completionID string, created int64, model string, content string, reasoning string, toolCalls []ToolCall, finishReason string) {
if finishReason == "" {
finishReason = "stop"
}
var contentVal interface{} = content
if len(toolCalls) > 0 && strings.TrimSpace(content) == "" {
contentVal = nil
}
resp := ChatCompletionResponse{
ID: completionID,
Object: "chat.completion",
Created: created,
Model: model,
Choices: []ChatCompletionResponseChoice{
{
Index: 0,
Message: ChatMessage{
Role: "assistant",
Content: contentVal,
ReasoningContent: reasoning,
ToolCalls: toolCalls,
},
FinishReason: finishReason,
},
},
Usage: Usage{
PromptTokens: 0,
CompletionTokens: 0,
TotalTokens: 0,
},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
// ---------------------------------------------------------------------------
// Pure Go Chromium CDP Engine & BrowserBridge
// ---------------------------------------------------------------------------
func findChromiumBinary(customPath string) string {
if customPath != "" {
if _, err := exec.LookPath(customPath); err == nil {
return customPath
}
}
candidates := []string{"chromium", "google-chrome", "chromium-browser", "chrome"}
for _, c := range candidates {
if p, err := exec.LookPath(c); err == nil {
return p
}
}
return ""
}
func dialCDPWebSocket(wsURL string) (net.Conn, *bufio.Reader, error) {
parts := strings.TrimPrefix(wsURL, "ws://")
slashIdx := strings.Index(parts, "/")
if slashIdx == -1 {
return nil, nil, fmt.Errorf("invalid websocket url: %s", wsURL)
}
host := parts[:slashIdx]
path := parts[slashIdx:]
conn, err := net.DialTimeout("tcp", host, 5*time.Second)
if err != nil {
return nil, nil, err
}
nonce := make([]byte, 16)
_, _ = rand.Read(nonce)
secKey := base64.StdEncoding.EncodeToString(nonce)
req := fmt.Sprintf("GET %s HTTP/1.1\r\nHost: %s\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: %s\r\nSec-WebSocket-Version: 13\r\n\r\n", path, host, secKey)
if _, err := conn.Write([]byte(req)); err != nil {
conn.Close()
return nil, nil, err
}
reader := bufio.NewReader(conn)
statusLine, err := reader.ReadString('\n')
if err != nil || !strings.Contains(statusLine, "101") {
conn.Close()
return nil, nil, fmt.Errorf("websocket upgrade failed: %s", statusLine)
}
for {
line, err := reader.ReadString('\n')
if err != nil || strings.TrimSpace(line) == "" {
break
}
}
return conn, reader, nil
}
func sendWSFrame(conn net.Conn, payload []byte) error {
var header []byte
header = append(header, 0x81)
length := len(payload)
var maskKey [4]byte
_, _ = rand.Read(maskKey[:])
if length < 126 {
header = append(header, byte(length)|0x80)
} else if length < 65536 {
header = append(header, 126|0x80)
header = append(header, byte(length>>8), byte(length&0xff))
} else {
header = append(header, 127|0x80)
for i := 7; i >= 0; i-- {
header = append(header, byte((length>>(i*8))&0xff))
}
}
header = append(header, maskKey[:]...)
masked := make([]byte, length)
for i := 0; i < length; i++ {
masked[i] = payload[i] ^ maskKey[i%4]
}
_, err := conn.Write(append(header, masked...))
return err
}
func readWSFrame(conn net.Conn, reader *bufio.Reader) ([]byte, error) {
conn.SetReadDeadline(time.Now().Add(35 * time.Second))
b1, err := reader.ReadByte()
if err != nil {
return nil, err
}
opcode := b1 & 0x0f
if opcode == 0x08 {
return nil, fmt.Errorf("websocket closed by server")
}
b2, err := reader.ReadByte()
if err != nil {
return nil, err
}
isMasked := (b2 & 0x80) != 0
length := int(b2 & 0x7f)
if length == 126 {
var extLen uint16
if err := binary.Read(reader, binary.BigEndian, &extLen); err != nil {
return nil, err
}
length = int(extLen)
} else if length == 127 {
var extLen uint64
if err := binary.Read(reader, binary.BigEndian, &extLen); err != nil {
return nil, err
}
length = int(extLen)
}
var maskKey [4]byte
if isMasked {
if _, err := io.ReadFull(reader, maskKey[:]); err != nil {
return nil, err
}
}
payload := make([]byte, length)
if _, err := io.ReadFull(reader, payload); err != nil {
return nil, err
}
if isMasked {
for i := 0; i < length; i++ {
payload[i] ^= maskKey[i%4]
}
}
return payload, nil
}
func sendCDPCommand(conn net.Conn, reader *bufio.Reader, method string, params map[string]interface{}) (map[string]interface{}, error) {
cmdID := int(atomic.AddInt64(&cdpCmdCounter, 1))
msg := map[string]interface{}{
"id": cmdID,
"method": method,
"params": params,
}
b, _ := json.Marshal(msg)
if err := sendWSFrame(conn, b); err != nil {
return nil, err
}
for {
frame, err := readWSFrame(conn, reader)
if err != nil {
return nil, err
}
var res map[string]interface{}
if err := json.Unmarshal(frame, &res); err == nil {
if idVal, ok := res["id"].(float64); ok && int(idVal) == cmdID {
return res, nil
}
}
}
}
type BrowserBridge struct {
mu sync.Mutex
cmd *exec.Cmd
xvfbCmd *exec.Cmd
tmpDir string
port string
conn net.Conn
reader *bufio.Reader
browserBin string
userAgent string
headless bool
useXvfb bool
socksProxy string
targetURL string
availableModels []string
selectedModel string
}
func NewBrowserBridge(browserBin, userAgent string, headless, useXvfb bool, socksProxy, targetURL string) *BrowserBridge {
if userAgent == "" {
userAgent = DefaultUserAgent
}
if targetURL == "" {
targetURL = DefaultTargetURL
}
return &BrowserBridge{
browserBin: browserBin,
userAgent: userAgent,
headless: headless,
useXvfb: useXvfb,
socksProxy: socksProxy,
targetURL: targetURL,
}
}
func (bb *BrowserBridge) Start() error {
bb.mu.Lock()
defer bb.mu.Unlock()
if bb.conn != nil {
return nil
}
binPath := findChromiumBinary(bb.browserBin)
if binPath == "" {
return fmt.Errorf("no chromium or google-chrome binary found on system")
}
var effectiveDisplay string
if bb.useXvfb {
xvfbPath, err := exec.LookPath("Xvfb")
if err != nil {
xvfbPath, err = exec.LookPath("Xfbdev")
}
if err != nil {
log.Printf("warning: Xvfb not found on system, falling back to offscreen flags")
} else {
vDisplay := findFreeXDisplay()
xCmd := exec.Command(xvfbPath, vDisplay, "-screen", "0", "1280x800x24", "-ac", "-nolisten", "tcp")
if err := xCmd.Start(); err == nil {
bb.xvfbCmd = xCmd
effectiveDisplay = vDisplay
time.Sleep(300 * time.Millisecond)
log.Printf("Virtual X server started on display %s", vDisplay)
} else {
log.Printf("warning: failed to start virtual X server: %v", err)
}
}
}
if effectiveDisplay == "" {
effectiveDisplay = os.Getenv("DISPLAY")
}
tmpDir, err := os.MkdirTemp("", "groqqer_bridge_*")
if err != nil {
return fmt.Errorf("failed to create temp profile dir: %w", err)
}
bb.tmpDir = tmpDir
port, err := getFreePort()
if err != nil {
port = "9559"
}
bb.port = port
args := []string{
"--remote-debugging-port=" + port,
"--user-data-dir=" + tmpDir,
"--disable-gpu",
"--no-sandbox",
"--no-first-run",
"--no-default-browser-check",
"--disable-extensions",
"--disable-default-apps",
"--disable-background-networking",
"--disable-sync",
"--disable-translate",
"--mute-audio",
"--hide-scrollbars",
"--window-size=1280,800",
"--user-agent=" + bb.userAgent,
}
if bb.xvfbCmd != nil {
args = append(args, "--window-position=0,0")
} else {
args = append(args, "--window-position=-3000,-3000")
}
if bb.headless && bb.xvfbCmd == nil {
args = append(args, "--headless=new")
}
if bb.socksProxy != "" {
proxyArg := bb.socksProxy
if !strings.HasPrefix(proxyArg, "socks5://") && !strings.HasPrefix(proxyArg, "socks5h://") {
proxyArg = "socks5://" + proxyArg
}
args = append(args, "--proxy-server="+proxyArg)
}
args = append(args, bb.targetURL)
cmd := exec.Command(binPath, args...)
if effectiveDisplay != "" {
cmd.Env = append(os.Environ(), "DISPLAY="+effectiveDisplay)
} else {
cmd.Env = os.Environ()
}
if err := cmd.Start(); err != nil {
return fmt.Errorf("failed to launch chromium: %w", err)
}
bb.cmd = cmd
// Connect to CDP target
var pageWS string
for attempt := 0; attempt < 25; attempt++ {
time.Sleep(500 * time.Millisecond)
resp, err := http.Get("http://127.0.0.1:" + port + "/json/list")
if err != nil {
continue
}
var targets []map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&targets); err == nil {
resp.Body.Close()
for _, t := range targets {
if t["type"] == "page" {
if ws, ok := t["webSocketDebuggerUrl"].(string); ok && ws != "" {
pageWS = ws
break
}
}
}
if pageWS != "" {
break
}
} else {
resp.Body.Close()
}
}
if pageWS == "" {
return fmt.Errorf("timed out waiting for chromium CDP page target")
}
conn, reader, err := dialCDPWebSocket(pageWS)
if err != nil {
return fmt.Errorf("failed to dial CDP websocket: %w", err)
}
bb.conn = conn
bb.reader = reader
log.Printf("Browser bridge connected to %s", bb.targetURL)
return nil
}
func (bb *BrowserBridge) Evaluate(js string) (interface{}, error) {
res, err := sendCDPCommand(bb.conn, bb.reader, "Runtime.evaluate", map[string]interface{}{
"expression": js,
"returnByValue": true,
"awaitPromise": true,
})
if err != nil {
return nil, err
}
if r, ok := res["result"].(map[string]interface{}); ok {
if r2, ok := r["result"].(map[string]interface{}); ok {
return r2["value"], nil
}
}
return nil, nil
}
func (bb *BrowserBridge) WaitForReady(timeout time.Duration) error {
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
val, err := bb.Evaluate(`(() => {
const sb = document.querySelector('[data-testid="stSelectbox"]');
const ta = document.querySelector('textarea');
return sb && ta && !ta.disabled ? true : false;
})()`)
if err == nil && val == true {
return nil
}
time.Sleep(300 * time.Millisecond)
}
return fmt.Errorf("timed out waiting for Streamlit chat interface to render")
}
func (bb *BrowserBridge) DiscoverModels() ([]string, error) {
if err := bb.WaitForReady(20 * time.Second); err != nil {
return nil, err
}
res, err := bb.Evaluate(`(() => {
const listbox = document.querySelector('[role="listbox"]');
if (!listbox) {
const btn = document.querySelector('[data-testid="stSelectbox"] button[aria-label="Open"]');
if (btn) btn.click();
}
return new Promise((resolve) => {
const start = Date.now();
const interval = setInterval(() => {
const options = Array.from(document.querySelectorAll('[role="option"]')).map(el => el.innerText.trim()).filter(Boolean);
if (options.length > 0 || Date.now() - start > 3000) {
clearInterval(interval);
const btn = document.querySelector('[data-testid="stSelectbox"] button[aria-label="Open"]') || document.body;
btn.click();
resolve(options);
}
}, 50);
});
})()`)
if err != nil {
return nil, err
}
var rawList []string
if arr, ok := res.([]interface{}); ok {
for _, item := range arr {
if str, ok := item.(string); ok && str != "" {
rawList = append(rawList, str)
}
}
}
var modelIDs []string
for _, item := range rawList {
cleanID := item
if idx := strings.Index(item, " ("); idx != -1 {
cleanID = strings.TrimSpace(item[:idx])
}
if cleanID != "" {
modelIDs = append(modelIDs, cleanID)
}
}
curr, _ := bb.Evaluate(`(() => {
const input = document.querySelector('[data-testid="stSelectbox"] input');
return input ? input.value : '';
})()`)
if currStr, ok := curr.(string); ok && currStr != "" {
cleanCurr := currStr
if idx := strings.Index(currStr, " ("); idx != -1 {
cleanCurr = strings.TrimSpace(currStr[:idx])
}
bb.selectedModel = cleanCurr
}
if len(modelIDs) > 0 {
bb.availableModels = modelIDs
}
return modelIDs, nil
}
func (bb *BrowserBridge) SwitchModel(modelID string) error {
if modelID == "" {
return nil
}
targetSubstr := strings.ToLower(strings.TrimSpace(modelID))
if strings.Contains(targetSubstr, "/") {
parts := strings.Split(targetSubstr, "/")
targetSubstr = parts[len(parts)-1]
}
targetJSON, _ := json.Marshal(targetSubstr)
js := fmt.Sprintf(`(() => {
const target = %s;
const input = document.querySelector('[data-testid="stSelectbox"] input');
if (input && input.value.toLowerCase().includes(target)) {
return Promise.resolve({ success: true, model: input.value });
}
const listbox = document.querySelector('[role="listbox"]');
if (!listbox) {
const btn = document.querySelector('[data-testid="stSelectbox"] button[aria-label="Open"]') || input;
if (btn) btn.click();
}
return new Promise((resolve) => {
const start = Date.now();
const interval = setInterval(() => {
const options = Array.from(document.querySelectorAll('[role="option"]'));
if (options.length > 0) {
const matched = options.find(o => o.innerText.toLowerCase().includes(target));
if (matched) {
clearInterval(interval);
const chosenText = matched.innerText;
matched.click();
resolve({ success: true, model: chosenText });
return;
}
}
if (Date.now() - start > 3000) {
clearInterval(interval);
const btn = document.querySelector('[data-testid="stSelectbox"] button[aria-label="Open"]') || document.body;
btn.click();
resolve({ success: false, model: '', error: 'Option not found' });
}
}, 50);
});
})()`, string(targetJSON))
res, err := bb.Evaluate(js)
if err != nil {
return err
}
if rMap, ok := res.(map[string]interface{}); ok {
if success, _ := rMap["success"].(bool); success {
chosen, _ := rMap["model"].(string)
cleanCurr := chosen
if idx := strings.Index(chosen, " ("); idx != -1 {
cleanCurr = strings.TrimSpace(chosen[:idx])
}
bb.selectedModel = cleanCurr
log.Printf("Successfully switched model to: %s", cleanCurr)
time.Sleep(1 * time.Second)
return nil
}
}
log.Printf("Model %s not found in selectbox, keeping current model %s", modelID, bb.selectedModel)
return nil
}
func (bb *BrowserBridge) ResetChat() error {
res, err := bb.Evaluate(`(() => {
const msgs = document.querySelectorAll('[data-testid="stChatMessage"]');
const retryBtn = Array.from(document.querySelectorAll('button')).find(b => b.innerText.includes('Continue without retrying'));
const clearBtn = Array.from(document.querySelectorAll('button')).find(b => b.innerText.includes('Clear Chat'));
const ta = document.querySelector('textarea');
if (msgs.length === 0 && !retryBtn && ta && !ta.disabled) {
return 'clean';
}
if (clearBtn) {
clearBtn.click();
return 'cleared';
}
if (retryBtn) {
retryBtn.click();
return 'retried';
}
return 'none';
})()`)
if err != nil {
return err
}
if res == "clean" {
return nil
}
deadline := time.Now().Add(6 * time.Second)
for time.Now().Before(deadline) {
time.Sleep(100 * time.Millisecond)
ready, _ := bb.Evaluate(`(() => {
const msgs = document.querySelectorAll('[data-testid="stChatMessage"]');
const ta = document.querySelector('textarea[data-testid="stChatInputTextArea"]') || document.querySelector('textarea');
const retryBtn = Array.from(document.querySelectorAll('button')).find(b => b.innerText.includes('Continue without retrying'));
if (retryBtn) {
retryBtn.click();
return false;
}
return msgs.length === 0 && ta && !ta.disabled;
})()`)
if ready == true {
return nil
}
}
return nil
}
func (bb *BrowserBridge) SubmitPrompt(promptText string) error {
promptJSON, err := json.Marshal(promptText)
if err != nil {
return err
}
// Ensure textarea is present and enabled
deadline := time.Now().Add(10 * time.Second)
for time.Now().Before(deadline) {
val, _ := bb.Evaluate(`(() => {
const ta = document.querySelector('textarea');
return ta && !ta.disabled;
})()`)
if val == true {
break
}
time.Sleep(100 * time.Millisecond)
}
js := fmt.Sprintf(`(() => {
const ta = document.querySelector('textarea[data-testid="stChatInputTextArea"]') || document.querySelector('textarea');
if (!ta) return 'no textarea';
ta.focus();
const nativeSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set;
nativeSetter.call(ta, %s);
ta.dispatchEvent(new Event('input', { bubbles: true }));
ta.dispatchEvent(new Event('change', { bubbles: true }));
const btn = document.querySelector('button[data-testid="stChatInputSubmitButton"]');
if (btn) {
btn.removeAttribute('disabled');
btn.disabled = false;
btn.click();
return 'clicked';
}
ta.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true }));
return 'enter';
})()`, string(promptJSON))
_, err = bb.Evaluate(js)
if err != nil {
return err
}
// Verify that new message is accepted by checking if count > 0
confirmDeadline := time.Now().Add(4 * time.Second)
for time.Now().Before(confirmDeadline) {
currCountVal, _ := bb.Evaluate(`document.querySelectorAll('[data-testid="stChatMessage"]').length`)
if c, ok := currCountVal.(float64); ok && int(c) > 0 {
return nil
}
time.Sleep(100 * time.Millisecond)
}
// Retry clicking if not registered yet
bb.Evaluate(`(() => {
const btn = document.querySelector('button[data-testid="stChatInputSubmitButton"]');
if (btn) btn.click();
const ta = document.querySelector('textarea');
if (ta) ta.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true }));
})()`)
return nil
}
func (bb *BrowserBridge) Close() {
bb.mu.Lock()
defer bb.mu.Unlock()
if bb.conn != nil {
bb.conn.Close()
bb.conn = nil
}
if bb.cmd != nil && bb.cmd.Process != nil {
_ = bb.cmd.Process.Kill()
_ = bb.cmd.Wait()
bb.cmd = nil
}
if bb.xvfbCmd != nil && bb.xvfbCmd.Process != nil {
_ = bb.xvfbCmd.Process.Kill()
_ = bb.xvfbCmd.Wait()
bb.xvfbCmd = nil
}
if bb.tmpDir != "" {
_ = os.RemoveAll(bb.tmpDir)
bb.tmpDir = ""
}
}
// ---------------------------------------------------------------------------
// Gateway Controller & Request Orchestration
// ---------------------------------------------------------------------------
type GroqqerGateway struct {
bridge *BrowserBridge
models []ModelItem
modelsMu sync.RWMutex
defaultModel string
port int
}
func DefaultFallbackModels() []string {
return []string{
"openai/gpt-oss-20b",
"openai/gpt-oss-120b",
"qwen/qwen3.6-27b",
"qwen/qwen3.8-27b",
"groq/compound",
"groq/compound-mini",
"allam-2-7b",
"llama-3.3-70b-versatile",
"llama-3.1-8b-instant",
"meta-llama/llama-4-scout-17b-16e-instruct",
}
}
func (g *GroqqerGateway) RefreshModels() {
discovered, err := g.bridge.DiscoverModels()
if err != nil || len(discovered) == 0 {
discovered = DefaultFallbackModels()
}
g.modelsMu.Lock()
defer g.modelsMu.Unlock()
now := time.Now().Unix()
var items []ModelItem
for _, id := range discovered {
items = append(items, ModelItem{
ID: id,
Object: "model",
Created: now,
OwnedBy: "groq",
})
}
g.models = items
}
func (g *GroqqerGateway) GetModels() []ModelItem {
g.modelsMu.RLock()
defer g.modelsMu.RUnlock()
if len(g.models) == 0 {
now := time.Now().Unix()
var items []ModelItem
for _, id := range DefaultFallbackModels() {
items = append(items, ModelItem{
ID: id,
Object: "model",
Created: now,
OwnedBy: "groq",
})
}
return items
}
out := make([]ModelItem, len(g.models))
copy(out, g.models)
return out
}
func (g *GroqqerGateway) HandleModels(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
models := g.GetModels()
resp := ModelsResponse{
Object: "list",
Data: models,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
func (g *GroqqerGateway) HandleChatCompletions(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var req ChatCompletionRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid JSON payload", http.StatusBadRequest)
return
}
if len(req.Messages) == 0 {
http.Error(w, "messages array must not be empty", http.StatusBadRequest)
return
}
requestedModel := req.Model
if requestedModel == "" {
requestedModel = g.defaultModel
}
promptText := FormatPrompt(req)
completionID := "chatcmpl-" + GenerateUUID()
created := time.Now().Unix()
// Lock bridge for exclusive session execution
g.bridge.mu.Lock()
defer g.bridge.mu.Unlock()
log.Printf("Handling completion: model=%s, stream=%v, messages=%d", requestedModel, req.Stream, len(req.Messages))
// 1. Switch model if needed
if err := g.bridge.SwitchModel(requestedModel); err != nil {
log.Printf("model switch warning: %v", err)
}
// 2. Ensure chat session is clean before submitting
if err := g.bridge.ResetChat(); err != nil {
log.Printf("reset chat warning: %v", err)
}
// 3. Submit the prompt
if err := g.bridge.SubmitPrompt(promptText); err != nil {
log.Printf("submit prompt error: %v", err)
http.Error(w, "Failed to submit prompt to target space: "+err.Error(), http.StatusInternalServerError)
return
}
effectiveModel := g.bridge.selectedModel
if effectiveModel == "" {
effectiveModel = requestedModel
}
targetMinCount := 2
// 4. Handle response: streaming or non-streaming
if req.Stream {
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
return
}
streamer := NewStreamer(w, flusher, completionID, created, effectiveModel)
streamer.Role()
thinkingFilter := NewStreamThinkingFilter()
toolFilter := NewStreamToolCallFilter()
lastLen := 0
start := time.Now()
finishReason := "stop"
var fullCaptured strings.Builder
evalJS := fmt.Sprintf(`(() => {
const msgs = Array.from(document.querySelectorAll('[data-testid="stChatMessage"]'));
if (msgs.length < %d) {
const alertEl = document.querySelector('[data-testid="stAlert"], [data-testid="stNotification"]');
if (alertEl) {
return { text: '', done: true, error: alertEl.innerText };
}
return { text: '', done: false, error: '' };
}
const lastMsg = msgs[msgs.length - 1];
const md = lastMsg.querySelector('[data-testid="stMarkdownContainer"]');
const text = md ? md.innerText : '';
const ta = document.querySelector('textarea');
const hasModelCaption = lastMsg.innerText.includes('Model:') || !!lastMsg.querySelector('[data-testid="stCaptionContainer"]');
const alertEl = lastMsg.querySelector('[data-testid="stAlert"], [data-testid="stNotification"]');
const errText = alertEl ? alertEl.innerText : '';
const isDone = ((!ta || !ta.disabled) && hasModelCaption) || (errText !== '');
return { text, done: isDone, error: errText };
})()`, targetMinCount)
for time.Since(start) < 120*time.Second {
time.Sleep(35 * time.Millisecond)
res, err := g.bridge.Evaluate(evalJS)
if err != nil {
continue
}
if rMap, ok := res.(map[string]interface{}); ok {
txt, _ := rMap["text"].(string)
done, _ := rMap["done"].(bool)
errTxt, _ := rMap["error"].(string)
if errTxt != "" && strings.TrimSpace(txt) == "" {
txt = errTxt
}
if len(txt) > lastLen {
delta := txt[lastLen:]
lastLen = len(txt)
fullCaptured.WriteString(delta)
thinkingFilter.Feed(delta,
func(cleanContent string) {
toolFilter.Feed(cleanContent,
func(userText string) {
streamer.Content(userText)
},
func(tc ToolCall) {
streamer.ToolCall(tc)
},
)
},
func(reasoning string) {
streamer.Reasoning(reasoning)
},
)
}
if done && (len(txt) > 0 || errTxt != "") {
break
}
}
}
thinkingFilter.Flush(
func(cleanContent string) {
toolFilter.Feed(cleanContent,
func(userText string) {
streamer.Content(userText)
},
func(tc ToolCall) {
streamer.ToolCall(tc)
},
)
},
func(reasoning string) {
streamer.Reasoning(reasoning)
},
)
toolFilter.Flush(
func(userText string) {
streamer.Content(userText)
},
func(tc ToolCall) {
streamer.ToolCall(tc)
},
)
if toolFilter.emittedCall {
finishReason = "tool_calls"
}
streamer.Finish(finishReason)
log.Printf("Streaming completion finished for %s in %v", effectiveModel, time.Since(start))
return
}
// Non-streaming response
start := time.Now()
var fullText string
evalJS := fmt.Sprintf(`(() => {
const msgs = Array.from(document.querySelectorAll('[data-testid="stChatMessage"]'));
if (msgs.length < %d) {
const alertEl = document.querySelector('[data-testid="stAlert"], [data-testid="stNotification"]');
if (alertEl) {
return { text: '', done: true, error: alertEl.innerText };
}
return { text: '', done: false, error: '' };
}
const lastMsg = msgs[msgs.length - 1];
const md = lastMsg.querySelector('[data-testid="stMarkdownContainer"]');
const text = md ? md.innerText : '';
const ta = document.querySelector('textarea');
const hasModelCaption = lastMsg.innerText.includes('Model:') || !!lastMsg.querySelector('[data-testid="stCaptionContainer"]');
const alertEl = lastMsg.querySelector('[data-testid="stAlert"], [data-testid="stNotification"]');
const errText = alertEl ? alertEl.innerText : '';
const isDone = ((!ta || !ta.disabled) && hasModelCaption) || (errText !== '');
return { text, done: isDone, error: errText };
})()`, targetMinCount)
for time.Since(start) < 120*time.Second {
time.Sleep(50 * time.Millisecond)
res, err := g.bridge.Evaluate(evalJS)
if err != nil {
continue
}
if rMap, ok := res.(map[string]interface{}); ok {
txt, _ := rMap["text"].(string)
done, _ := rMap["done"].(bool)
errTxt, _ := rMap["error"].(string)
if errTxt != "" && strings.TrimSpace(txt) == "" {
txt = errTxt
}
fullText = txt
if done && (len(txt) > 0 || errTxt != "") {
break
}
}
}
cleanContent, reasoningContent := ExtractThinking(fullText)
toolCalls, remainingContent, hasTools := DetectToolCalls(cleanContent)
finishReason := "stop"
if hasTools {
finishReason = "tool_calls"
cleanContent = remainingContent
}
WriteCompletionResponse(w, completionID, created, effectiveModel, cleanContent, reasoningContent, toolCalls, finishReason)
log.Printf("Completion finished for %s in %v", effectiveModel, time.Since(start))
}
// ---------------------------------------------------------------------------
// Main Entrypoint & Signal Handling
// ---------------------------------------------------------------------------
func main() {
port := flag.Int("port", 8080, "HTTP server listening port")
targetURL := flag.String("target", DefaultTargetURL, "Groq Streamlit space URL")
defaultModel := flag.String("default-model", DefaultModel, "Default model ID if unspecified")
browserBin := flag.String("browser", "", "Path to Chromium/Chrome binary")
userAgent := flag.String("user-agent", DefaultUserAgent, "User-Agent string")
flag.StringVar(userAgent, "ua", DefaultUserAgent, "User-Agent string (alias)")
useXvfb := flag.Bool("xvfb", true, "Manage virtual X server (Xvfb) for display isolation")
noXvfb := flag.Bool("no-xvfb", false, "Disable virtual X server")
headless := flag.Bool("headless", true, "Run Chromium in headless mode")
socksProxy := flag.String("socks", "", "SOCKS5 proxy (e.g. socks5://127.0.0.1:1080)")
flag.StringVar(socksProxy, "proxy", "", "Proxy address alias")
flag.StringVar(socksProxy, "socks5", "", "SOCKS5 proxy alias")
flag.Parse()
effectiveXvfb := *useXvfb
if *noXvfb {
effectiveXvfb = false
}
effectiveProxy := *socksProxy
if effectiveProxy == "" {
for _, envVar := range []string{"ALL_PROXY", "all_proxy", "SOCKS5_PROXY", "socks5_proxy", "SOCKS_PROXY", "socks_proxy"} {
if val := os.Getenv(envVar); val != "" {
effectiveProxy = val
break
}
}
}
bridge := NewBrowserBridge(*browserBin, *userAgent, *headless, effectiveXvfb, effectiveProxy, *targetURL)
log.Println("Starting groqqer browser bridge...")
if err := bridge.Start(); err != nil {
log.Fatalf("failed to start browser bridge: %v", err)
}
gateway := &GroqqerGateway{
bridge: bridge,
defaultModel: *defaultModel,
port: *port,
}
// Initial discovery of live models
go func() {
log.Println("Discovering available models from Streamlit UI...")
gateway.RefreshModels()
models := gateway.GetModels()
log.Printf("Ready with %d active models. Default: %s", len(models), gateway.defaultModel)
}()
mux := http.NewServeMux()
mux.HandleFunc("/v1/models", gateway.HandleModels)
mux.HandleFunc("/models", gateway.HandleModels)
mux.HandleFunc("/v1/chat/completions", gateway.HandleChatCompletions)
mux.HandleFunc("/chat/completions", gateway.HandleChatCompletions)
// Root status handler
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"service": "groqqer",
"status": "running",
"target": *targetURL,
"default": *defaultModel,
"models": "/v1/models",
"endpoints": []string{"/v1/chat/completions", "/v1/models"},
})
})
server := &http.Server{
Addr: fmt.Sprintf(":%d", *port),
Handler: mux,
}
// Graceful shutdown handling
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
go func() {
<-sigChan
log.Println("Shutting down groqqer...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = server.Shutdown(ctx)
bridge.Close()
os.Exit(0)
}()
log.Printf("groqqer gateway listening on http://127.0.0.1:%d", *port)
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("server error: %v", err)
}
}