model list fixes

This commit is contained in:
Luxferre
2026-08-26 09:44:58 +03:00
parent 1d226ecf3f
commit 66d7bef117
+178 -13
View File
@@ -1771,6 +1771,130 @@ func (bb *BrowserBridge) RotateIdentity() (string, error) {
return "", fmt.Errorf("failed to rotate identity: %v", evalRes) return "", fmt.Errorf("failed to rotate identity: %v", evalRes)
} }
func (bb *BrowserBridge) EvaluateScript(js string) (string, error) {
bb.mu.Lock()
defer bb.mu.Unlock()
if bb.conn == nil {
if err := bb.Start(); err != nil {
return "", fmt.Errorf("failed to start browser bridge: %w", err)
}
}
evalRes, err := sendCDPCommand(bb.conn, bb.reader, "Runtime.evaluate", map[string]interface{}{
"expression": js,
"awaitPromise": true,
"returnByValue": true,
})
if err != nil {
return "", err
}
resJSON, _ := json.Marshal(evalRes)
return string(resJSON), nil
}
func (bb *BrowserBridge) FetchFreeModels() ([]ModelItem, error) {
js := `(async () => {
let targetUrl = null;
for (let attempt = 0; attempt < 10; attempt++) {
const entries = performance.getEntriesByType('resource')
.map(r => r.name)
.filter(n => n.includes('model-helpers') || n.includes('model-selector'));
targetUrl = entries.find(n => n.includes('model-helpers'));
if (targetUrl) break;
const scripts = Array.from(document.querySelectorAll('script[src]')).map(s => s.src);
targetUrl = scripts.find(s => s.includes('model-helpers'));
if (targetUrl) break;
await new Promise(r => setTimeout(r, 500));
}
if (!targetUrl) return { error: "model-helpers bundle not found in active session" };
const resp = await fetch(targetUrl);
const code = await resp.text();
const startIdx = code.indexOf('var dE=[');
if (startIdx === -1) return { error: "model registry definition (var dE) not found" };
const sandbox = 'var Z = function(d){return new Date(d)}; function uE(){return{}}; function $(e){return e}; var fast={},images={},reasoning={},reasoningEffort={},toolCalling={},imageGeneration={},nativePDFs={}; ' + code.substring(0, startIdx) + code.substring(startIdx, code.indexOf(';function ', startIdx)) + '; dE;';
const models = eval(sandbox);
const now = new Date();
const freeList = [];
for (const m of models) {
const isRetired = m.retiredOn && new Date(m.retiredOn) <= now;
const isLegacy = !!m.legacy;
const hasExplicitFree = m.limits && m.limits.app && m.limits.app.free !== undefined && m.limits.app.free !== null;
const isPro = m.tier === 'pro' || m.tier === 'premier' || !!m.requiresPro || !!m.premium || !!m.disabled;
if (!isLegacy && !isRetired && !isPro && hasExplicitFree) {
let dev = (m.developer || m.provider || "t3").toLowerCase();
if (dev.includes("google")) dev = "google";
else if (dev.includes("openai")) dev = "openai";
else if (dev.includes("anthropic")) dev = "anthropic";
else if (dev.includes("meta")) dev = "meta";
else if (dev.includes("moonshot")) dev = "moonshot";
else if (dev.includes("alibaba") || dev.includes("qwen")) dev = "alibaba";
else if (dev.includes("deepseek")) dev = "deepseek";
else if (dev.includes("xai") || dev.includes("grok")) dev = "xai";
else if (dev.includes("z.ai") || dev.includes("glm")) dev = "zai";
else if (dev.includes("xiaomi")) dev = "xiaomi";
else if (dev.includes("minimax")) dev = "minimax";
freeList.push({
id: m.id,
owned_by: dev
});
}
}
return { models: freeList };
})()`
res, err := bb.EvaluateScript(js)
if err != nil {
return nil, err
}
var cdpRes struct {
Result struct {
Result struct {
Value struct {
Error string `json:"error"`
Models []struct {
ID string `json:"id"`
OwnedBy string `json:"owned_by"`
} `json:"models"`
} `json:"value"`
} `json:"result"`
} `json:"result"`
}
if err := json.Unmarshal([]byte(res), &cdpRes); err != nil {
return nil, err
}
if cdpRes.Result.Result.Value.Error != "" {
return nil, fmt.Errorf("%s", cdpRes.Result.Result.Value.Error)
}
var items []ModelItem
now := time.Now().Unix()
for _, m := range cdpRes.Result.Result.Value.Models {
if m.ID != "" {
items = append(items, ModelItem{
ID: m.ID,
Object: "model",
Created: now,
OwnedBy: m.OwnedBy,
})
}
}
return items, nil
}
func (bb *BrowserBridge) ExecuteFetch(payload T3ChatPayload, deploymentID, clientContext string) (int, string, error) { func (bb *BrowserBridge) ExecuteFetch(payload T3ChatPayload, deploymentID, clientContext string) (int, string, error) {
bb.mu.Lock() bb.mu.Lock()
defer bb.mu.Unlock() defer bb.mu.Unlock()
@@ -2162,6 +2286,24 @@ type T3Gateway struct {
autoCapture bool autoCapture bool
client *http.Client client *http.Client
bridge *BrowserBridge bridge *BrowserBridge
models []ModelItem
}
func DefaultFreeModels() []ModelItem {
now := time.Now().Unix()
return []ModelItem{
// Google (Verified Unlocked Free)
{ID: "gemini-3.5-flash-lite", Object: "model", Created: now, OwnedBy: "google"},
{ID: "gemini-3.1-flash-lite", Object: "model", Created: now, OwnedBy: "google"},
{ID: "gemini-3-flash", Object: "model", Created: now, OwnedBy: "google"},
// OpenAI (Verified Unlocked Free)
{ID: "gpt-5.4-mini", Object: "model", Created: now, OwnedBy: "openai"},
// Moonshot (Verified Unlocked Free)
{ID: "kimi-k2-0905", Object: "model", Created: now, OwnedBy: "moonshot"},
{ID: "kimi-k2.5", Object: "model", Created: now, OwnedBy: "moonshot"},
}
} }
func NewT3Gateway(targetURL, clientContext, deploymentID, cookie, hcaptchaToken, defaultModel, browserBin string, autoCapture bool, headless bool, display string, useXvfb bool) *T3Gateway { func NewT3Gateway(targetURL, clientContext, deploymentID, cookie, hcaptchaToken, defaultModel, browserBin string, autoCapture bool, headless bool, display string, useXvfb bool) *T3Gateway {
@@ -2200,6 +2342,7 @@ func NewT3Gateway(targetURL, clientContext, deploymentID, cookie, hcaptchaToken,
autoCapture: autoCapture, autoCapture: autoCapture,
client: &http.Client{Timeout: 300 * time.Second}, client: &http.Client{Timeout: 300 * time.Second},
bridge: bridge, bridge: bridge,
models: DefaultFreeModels(),
} }
} }
@@ -2226,20 +2369,35 @@ func (g *T3Gateway) GetCredentials() (cookie, token, deploymentID, clientContext
return g.cookie, g.hcaptchaToken, g.deploymentID, g.clientContext return g.cookie, g.hcaptchaToken, g.deploymentID, g.clientContext
} }
func (g *T3Gateway) ListModels() []ModelItem { func (g *T3Gateway) RefreshModels() error {
now := time.Now().Unix() if g.bridge != nil {
return []ModelItem{ items, err := g.bridge.FetchFreeModels()
{ID: "gemini-3.5-flash-lite", Object: "model", Created: now, OwnedBy: "google"}, if err != nil {
{ID: "gemini-2.5-pro", Object: "model", Created: now, OwnedBy: "google"}, return err
{ID: "gemini-2.5-flash", Object: "model", Created: now, OwnedBy: "google"},
{ID: "claude-3-7-sonnet", Object: "model", Created: now, OwnedBy: "anthropic"},
{ID: "claude-3-5-sonnet", Object: "model", Created: now, OwnedBy: "anthropic"},
{ID: "gpt-4o", Object: "model", Created: now, OwnedBy: "openai"},
{ID: "gpt-4o-mini", Object: "model", Created: now, OwnedBy: "openai"},
{ID: "o3-mini", Object: "model", Created: now, OwnedBy: "openai"},
{ID: "deepseek-r1", Object: "model", Created: now, OwnedBy: "deepseek"},
{ID: "deepseek-v3", Object: "model", Created: now, OwnedBy: "deepseek"},
} }
if len(items) > 0 {
g.mu.Lock()
g.models = items
g.mu.Unlock()
return nil
}
}
return nil
}
func (g *T3Gateway) ListModels() []ModelItem {
g.mu.RLock()
defer g.mu.RUnlock()
if len(g.models) == 0 {
return DefaultFreeModels()
}
now := time.Now().Unix()
res := make([]ModelItem, len(g.models))
for i, m := range g.models {
res[i] = m
res[i].Created = now
}
return res
} }
func (g *T3Gateway) extractSessionID(cookieStr string) string { func (g *T3Gateway) extractSessionID(cookieStr string) string {
@@ -2999,6 +3157,13 @@ func main() {
gateway.bridge = nil gateway.bridge = nil
} else { } else {
fmt.Println("Browser bridge active! Upstream requests will execute with genuine Chromium TLS fingerprint.") fmt.Println("Browser bridge active! Upstream requests will execute with genuine Chromium TLS fingerprint.")
fmt.Println("Fetching live free-tier models to populate /v1/models...")
if err := gateway.RefreshModels(); err != nil {
fmt.Fprintf(os.Stderr, "warning: live model discovery failed: %v (using default free-tier model catalog)\n", err)
} else {
models := gateway.ListModels()
fmt.Printf("Successfully populated %d active free-tier models into /v1/models\n", len(models))
}
} }
} }