mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-09-02 10:19:49 +00:00
Merge remote-tracking branch 'origin/main' into dev
This commit is contained in:
@@ -41,7 +41,9 @@ func registerProviders(registry *providers.Registry, cfg *config.Config) {
|
||||
}
|
||||
|
||||
if cfg.Providers.OpenRouter.APIKey != "" {
|
||||
registry.Register(providers.NewOpenAIProvider("openrouter", cfg.Providers.OpenRouter.APIKey, "https://openrouter.ai/api/v1", "anthropic/claude-sonnet-4-5-20250929"))
|
||||
orProv := providers.NewOpenAIProvider("openrouter", cfg.Providers.OpenRouter.APIKey, "https://openrouter.ai/api/v1", "anthropic/claude-sonnet-4-5-20250929")
|
||||
orProv.WithSiteInfo("https://goclaw.sh", "GoClaw")
|
||||
registry.Register(orProv)
|
||||
slog.Info("registered provider", "name", "openrouter")
|
||||
}
|
||||
|
||||
@@ -404,6 +406,9 @@ func registerProvidersFromDB(registry *providers.Registry, provStore store.Provi
|
||||
if p.ProviderType == store.ProviderMiniMax {
|
||||
prov.WithChatPath("/text/chatcompletion_v2")
|
||||
}
|
||||
if p.ProviderType == store.ProviderOpenRouter {
|
||||
prov.WithSiteInfo("https://goclaw.sh", "GoClaw")
|
||||
}
|
||||
registry.RegisterForTenant(p.TenantID, prov)
|
||||
}
|
||||
slog.Info("registered provider from DB", "name", p.Name)
|
||||
|
||||
@@ -55,7 +55,14 @@ func buildMCPToolsInlineSection(descs map[string]string) []string {
|
||||
mcpOptionalParamInstruction,
|
||||
"",
|
||||
}
|
||||
for name, desc := range descs {
|
||||
// Sort MCP tool names for deterministic ordering — critical for prompt caching.
|
||||
sortedNames := make([]string, 0, len(descs))
|
||||
for name := range descs {
|
||||
sortedNames = append(sortedNames, name)
|
||||
}
|
||||
slices.Sort(sortedNames)
|
||||
for _, name := range sortedNames {
|
||||
desc := descs[name]
|
||||
if len(desc) > mcpToolDescMaxLen {
|
||||
desc = desc[:mcpToolDescMaxLen] + "…"
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ type OpenAIProvider struct {
|
||||
authPrefix string // auth header prefix, defaults to "Bearer " if empty
|
||||
defaultModel string
|
||||
providerType string // DB provider_type (e.g. "gemini_native", "openai", "minimax_native")
|
||||
siteURL string // optional site URL for provider identification (e.g. OpenRouter HTTP-Referer)
|
||||
siteTitle string // optional site title for provider identification (e.g. OpenRouter X-Title)
|
||||
client *http.Client
|
||||
retryConfig RetryConfig
|
||||
}
|
||||
@@ -114,6 +116,14 @@ func (p *OpenAIProvider) WithAuthPrefix(prefix string) *OpenAIProvider {
|
||||
return p
|
||||
}
|
||||
|
||||
// WithSiteInfo sets site identification headers sent with API requests.
|
||||
// Used by OpenRouter for rankings (HTTP-Referer, X-Title).
|
||||
func (p *OpenAIProvider) WithSiteInfo(url, title string) *OpenAIProvider {
|
||||
p.siteURL = url
|
||||
p.siteTitle = title
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *OpenAIProvider) Name() string { return p.name }
|
||||
func (p *OpenAIProvider) DefaultModel() string { return p.defaultModel }
|
||||
func (p *OpenAIProvider) SupportsThinking() bool { return true }
|
||||
@@ -576,6 +586,13 @@ func (p *OpenAIProvider) doRequest(ctx context.Context, body any) (io.ReadCloser
|
||||
}
|
||||
httpReq.Header.Set("Authorization", prefix+p.apiKey)
|
||||
}
|
||||
// OpenRouter identification headers for rankings/analytics
|
||||
if p.siteURL != "" {
|
||||
httpReq.Header.Set("HTTP-Referer", p.siteURL)
|
||||
}
|
||||
if p.siteTitle != "" {
|
||||
httpReq.Header.Set("X-Title", p.siteTitle)
|
||||
}
|
||||
|
||||
resp, err := p.client.Do(httpReq)
|
||||
if err != nil {
|
||||
|
||||
@@ -25,7 +25,7 @@ func (s *PGAgentStore) GetAgentContextFiles(ctx context.Context, agentID uuid.UU
|
||||
return nil, err
|
||||
}
|
||||
rows, err := s.db.QueryContext(ctx,
|
||||
"SELECT agent_id, file_name, content FROM agent_context_files WHERE agent_id = $1"+tClause,
|
||||
"SELECT agent_id, file_name, content FROM agent_context_files WHERE agent_id = $1"+tClause+" ORDER BY file_name",
|
||||
append([]any{agentID}, tArgs...)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -87,7 +87,7 @@ func (s *PGAgentStore) GetUserContextFiles(ctx context.Context, agentID uuid.UUI
|
||||
return nil, err
|
||||
}
|
||||
rows, err := s.db.QueryContext(ctx,
|
||||
"SELECT agent_id, user_id, file_name, content FROM user_context_files WHERE agent_id = $1 AND user_id = $2"+tClause,
|
||||
"SELECT agent_id, user_id, file_name, content FROM user_context_files WHERE agent_id = $1 AND user_id = $2"+tClause+" ORDER BY file_name",
|
||||
append([]any{agentID, userID}, tArgs...)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -27,7 +27,7 @@ func (s *SQLiteAgentStore) GetAgentContextFiles(ctx context.Context, agentID uui
|
||||
return nil, err
|
||||
}
|
||||
rows, err := s.db.QueryContext(ctx,
|
||||
"SELECT agent_id, file_name, content FROM agent_context_files WHERE agent_id = ?"+tClause,
|
||||
"SELECT agent_id, file_name, content FROM agent_context_files WHERE agent_id = ?"+tClause+" ORDER BY file_name",
|
||||
append([]any{agentID}, tArgs...)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -97,7 +97,7 @@ func (s *SQLiteAgentStore) GetUserContextFiles(ctx context.Context, agentID uuid
|
||||
return nil, err
|
||||
}
|
||||
rows, err := s.db.QueryContext(ctx,
|
||||
"SELECT agent_id, user_id, file_name, content FROM user_context_files WHERE agent_id = ? AND user_id = ?"+tClause,
|
||||
"SELECT agent_id, user_id, file_name, content FROM user_context_files WHERE agent_id = ? AND user_id = ?"+tClause+" ORDER BY file_name",
|
||||
append([]any{agentID, userID}, tArgs...)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -130,8 +130,16 @@ func (pe *PolicyEngine) FilterTools(
|
||||
}
|
||||
}
|
||||
|
||||
// Add registry aliases for allowed canonical tools
|
||||
for alias, canonical := range registry.Aliases() {
|
||||
// Add registry aliases for allowed canonical tools.
|
||||
// Sort alias names for deterministic ordering (prompt caching).
|
||||
aliasMap := registry.Aliases()
|
||||
aliasList := make([]string, 0, len(aliasMap))
|
||||
for alias := range aliasMap {
|
||||
aliasList = append(aliasList, alias)
|
||||
}
|
||||
slices.Sort(aliasList)
|
||||
for _, alias := range aliasList {
|
||||
canonical := aliasMap[alias]
|
||||
if !allowedSet[canonical] {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -222,18 +223,34 @@ func safeExecute(tool Tool, ctx context.Context, args map[string]any) (result *R
|
||||
|
||||
// ProviderDefs returns tool definitions for LLM provider APIs.
|
||||
// Includes alias definitions (same params/description, alias name).
|
||||
// Results are sorted by tool name for deterministic ordering (prompt caching).
|
||||
func (r *Registry) ProviderDefs() []providers.ToolDefinition {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
defs := make([]providers.ToolDefinition, 0, len(r.tools)+len(r.aliases))
|
||||
for name, tool := range r.tools {
|
||||
if r.disabled[name] {
|
||||
continue
|
||||
// Sort canonical tool names for deterministic ordering.
|
||||
sortedNames := make([]string, 0, len(r.tools))
|
||||
for name := range r.tools {
|
||||
if !r.disabled[name] {
|
||||
sortedNames = append(sortedNames, name)
|
||||
}
|
||||
defs = append(defs, ToProviderDef(tool))
|
||||
}
|
||||
for alias, canonical := range r.aliases {
|
||||
slices.Sort(sortedNames)
|
||||
|
||||
defs := make([]providers.ToolDefinition, 0, len(sortedNames)+len(r.aliases))
|
||||
for _, name := range sortedNames {
|
||||
defs = append(defs, ToProviderDef(r.tools[name]))
|
||||
}
|
||||
|
||||
// Sort alias names for deterministic ordering.
|
||||
sortedAliases := make([]string, 0, len(r.aliases))
|
||||
for alias := range r.aliases {
|
||||
sortedAliases = append(sortedAliases, alias)
|
||||
}
|
||||
slices.Sort(sortedAliases)
|
||||
|
||||
for _, alias := range sortedAliases {
|
||||
canonical := r.aliases[alias]
|
||||
if r.disabled[canonical] {
|
||||
continue
|
||||
}
|
||||
@@ -254,6 +271,8 @@ func (r *Registry) ProviderDefs() []providers.ToolDefinition {
|
||||
}
|
||||
|
||||
// List returns all registered canonical tool names (excludes aliases).
|
||||
// Results are sorted lexicographically for deterministic ordering — critical
|
||||
// for LLM prompt caching (Anthropic/OpenAI cache by exact prefix match).
|
||||
func (r *Registry) List() []string {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
@@ -263,6 +282,7 @@ func (r *Registry) List() []string {
|
||||
names = append(names, name)
|
||||
}
|
||||
}
|
||||
slices.Sort(names)
|
||||
return names
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user