mirror of
https://github.com/patriceckhart/zot.git
synced 2026-06-26 21:36:31 +02:00
- internal/provider/gemini.go: REST client against
generativelanguage.googleapis.com/v1beta/models/{id}:streamGenerateContent
?alt=sse, mapping our message/tool format to Gemini's Content/Part schema
and translating SSE chunks into the existing assistant-message event
stream. Handles text, tool calls, thought-summary parts, and per-model
thinking config (thinkingBudget for 2.5, thinkingLevel for 3.x with
Gemini-3-Pro pinned to LOW minimum).
- internal/provider/discover.go: DiscoverGoogle pages /v1beta/models and
filters to chat-capable ids (skips embeddings, AQA).
- internal/provider/models.go: catalog entries for gemini-2.5-pro,
2.5-flash, 2.5-flash-lite, 2.0-flash, 2.0-flash-lite.
- internal/auth: 'google' is a recognized provider; API-key probe hits
/v1beta/models with x-goog-api-key. OAuth flows reject google with a
clear 'API-key only' error since Gemini Advanced subscriptions don't
issue API tokens.
- internal/agent: env lookup for GEMINI_API_KEY / GOOGLE_API_KEY,
default model gemini-2.5-pro, NewClient wires provider.NewGemini,
background model discovery, /login + /logout + rescue dialog all
include google.
- README: new ### Google Gemini section with auth model, free-tier
limits, and reasoning-config notes.
98 lines
2.9 KiB
Go
98 lines
2.9 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/patriceckhart/zot/internal/provider"
|
|
)
|
|
|
|
// ModelCachePath returns the on-disk location of the merged model cache.
|
|
func ModelCachePath() string {
|
|
return filepath.Join(ZotHome(), "models-cache.json")
|
|
}
|
|
|
|
// UserModelsPath returns the path to the user's models.json override.
|
|
func UserModelsPath() string {
|
|
return filepath.Join(ZotHome(), "models.json")
|
|
}
|
|
|
|
// LoadCachedModels loads the cache file and applies it to the provider
|
|
// package so FindModel / ModelsForProvider see live ids immediately.
|
|
// Safe to call before any credentials are known.
|
|
func LoadCachedModels() {
|
|
c, err := provider.LoadCache(ModelCachePath())
|
|
if err != nil {
|
|
return
|
|
}
|
|
if len(c.Models) > 0 {
|
|
provider.SetLiveModels(c.Models)
|
|
}
|
|
}
|
|
|
|
// LoadUserModels reads $ZOT_HOME/models.json and merges any user-defined
|
|
// models into the active catalog. User models take highest precedence.
|
|
func LoadUserModels() {
|
|
models := provider.LoadUserModels(UserModelsPath())
|
|
provider.SetUserModels(models)
|
|
}
|
|
|
|
// RefreshModelsAsync kicks a background discovery for every provider
|
|
// we have credentials for. Refreshed results are merged into the
|
|
// active catalog and persisted to the on-disk cache.
|
|
//
|
|
// Silent on error: discovery is a nice-to-have. Callers can still use
|
|
// the baked-in catalog if this fails.
|
|
func RefreshModelsAsync() {
|
|
go refreshModels()
|
|
}
|
|
|
|
func refreshModels() {
|
|
cached, _ := provider.LoadCache(ModelCachePath())
|
|
if cached.IsFresh() {
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
|
defer cancel()
|
|
|
|
var all []provider.Model
|
|
|
|
if cred, method, err := ResolveCredential("anthropic", ""); err == nil && method == "apikey" {
|
|
// /v1/models on Anthropic is API-key only; OAuth tokens can
|
|
// also list models via the bearer header, but we skip OAuth
|
|
// here to avoid surprise rate-limit hits on subscription keys.
|
|
if live, err := provider.DiscoverAnthropic(ctx, cred, ""); err == nil {
|
|
all = append(all, live...)
|
|
}
|
|
}
|
|
if cred, method, err := ResolveCredential("openai", ""); err == nil && method == "apikey" {
|
|
if live, err := provider.DiscoverOpenAI(ctx, cred, ""); err == nil {
|
|
all = append(all, live...)
|
|
}
|
|
}
|
|
if cred, method, err := ResolveCredential("kimi", ""); err == nil && method == "apikey" {
|
|
if live, err := provider.DiscoverOpenAI(ctx, cred, "https://api.kimi.com/coding/v1"); err == nil {
|
|
for i := range live {
|
|
live[i].Provider = "kimi"
|
|
live[i].Source = "live"
|
|
}
|
|
all = append(all, live...)
|
|
}
|
|
}
|
|
if cred, method, err := ResolveCredential("google", ""); err == nil && method == "apikey" {
|
|
if live, err := provider.DiscoverGoogle(ctx, cred, ""); err == nil {
|
|
all = append(all, live...)
|
|
}
|
|
}
|
|
|
|
if len(all) == 0 {
|
|
return
|
|
}
|
|
provider.SetLiveModels(all)
|
|
_ = provider.SaveCache(ModelCachePath(), provider.ModelCache{
|
|
FetchedAt: time.Now().UTC(),
|
|
Models: all,
|
|
})
|
|
}
|