feat: implement OpenRouter Free default and paid model authorization check
This commit is contained in:
parent
985289a3b7
commit
d2366b4785
3 changed files with 100 additions and 3 deletions
|
|
@ -664,8 +664,29 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
|
|||
SendResponse: false,
|
||||
}
|
||||
|
||||
// context-dependent commands check their own Runtime fields and report
|
||||
// "unavailable" when the required capability is nil.
|
||||
// Check for pending authorization response
|
||||
if isYesResponse(msg.Content) {
|
||||
if pending, _ := agent.Sessions.GetMetadata(opts.SessionKey, "pending_paid_auth"); pending == true {
|
||||
agent.Sessions.SetMetadata(opts.SessionKey, "paid_model_authorized", true)
|
||||
agent.Sessions.SetMetadata(opts.SessionKey, "pending_paid_auth", false)
|
||||
|
||||
// Retrieve the stashed message to resume
|
||||
if stashed, ok := agent.Sessions.GetMetadata(opts.SessionKey, "stashed_user_message"); ok {
|
||||
opts.UserMessage = stashed.(string)
|
||||
// Clear stash
|
||||
agent.Sessions.SetMetadata(opts.SessionKey, "stashed_user_message", nil)
|
||||
|
||||
al.bus.PublishOutbound(ctx, bus.OutboundMessage{
|
||||
Channel: msg.Channel,
|
||||
ChatID: msg.ChatID,
|
||||
Content: "✅ Authorized! Processing your request with the advanced model now...",
|
||||
})
|
||||
} else {
|
||||
return "✅ Authorized! DeepSeek and other paid models are now enabled for this session. What would you like me to do?", nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if response, handled := al.handleCommand(ctx, msg, agent, &opts); handled {
|
||||
return response, nil
|
||||
}
|
||||
|
|
@ -926,6 +947,20 @@ func (al *AgentLoop) runLLMIteration(
|
|||
// tool chain doesn't switch models mid-way through.
|
||||
activeCandidates, activeModel := al.selectCandidates(agent, opts.UserMessage, messages)
|
||||
|
||||
// Check for paid model authorization
|
||||
if isPaidModel(activeModel) {
|
||||
authorized, _ := agent.Sessions.GetMetadata(opts.SessionKey, "paid_model_authorized")
|
||||
if authorized != true {
|
||||
// Stash the message and ask for permission
|
||||
agent.Sessions.SetMetadata(opts.SessionKey, "pending_paid_auth", true)
|
||||
agent.Sessions.SetMetadata(opts.SessionKey, "stashed_user_message", opts.UserMessage)
|
||||
agent.Sessions.Save(opts.SessionKey)
|
||||
|
||||
authMsg := fmt.Sprintf("🛡️ **Authorization Required**\n\nI've detected this task requires an advanced model (%s) which may incur costs. \n\nReply with **'yes'** or **'y'** to authorize this session, or I can continue with the free model if you prefer.", activeModel)
|
||||
return authMsg, 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
for iteration < agent.MaxIterations {
|
||||
iteration++
|
||||
|
||||
|
|
@ -1865,3 +1900,22 @@ func extractParentPeer(msg bus.InboundMessage) *routing.RoutePeer {
|
|||
}
|
||||
return &routing.RoutePeer{Kind: parentKind, ID: parentID}
|
||||
}
|
||||
|
||||
func isPaidModel(model string) bool {
|
||||
m := strings.ToLower(model)
|
||||
// Explicitly free models
|
||||
if strings.Contains(m, "/free") || strings.HasPrefix(m, "ollama/") || strings.HasPrefix(m, "vllm/") {
|
||||
return false
|
||||
}
|
||||
// Models on OpenRouter that are free or we want to treat as free
|
||||
if strings.HasPrefix(m, "openrouter/free") {
|
||||
return false
|
||||
}
|
||||
// Everything else (DeepSeek, Claude, GPT-4, etc.) is considered paid
|
||||
return true
|
||||
}
|
||||
|
||||
func isYesResponse(content string) bool {
|
||||
c := strings.ToLower(strings.TrimSpace(content))
|
||||
return c == "yes" || c == "y" || c == "ok" || c == "confirm" || c == "proceed"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@ func DefaultConfig() *Config {
|
|||
Workspace: workspacePath,
|
||||
RestrictToWorkspace: true,
|
||||
Provider: "",
|
||||
Model: "",
|
||||
ModelName: "openrouter-free",
|
||||
Model: "openrouter/free",
|
||||
MaxTokens: 32768,
|
||||
Temperature: nil, // nil means use provider default
|
||||
MaxToolIterations: 10,
|
||||
|
|
@ -248,6 +249,12 @@ func DefaultConfig() *Config {
|
|||
},
|
||||
|
||||
// OpenRouter (100+ models) - https://openrouter.ai/keys
|
||||
{
|
||||
ModelName: "openrouter-free",
|
||||
Model: "openrouter/free",
|
||||
APIBase: "https://openrouter.ai/api/v1",
|
||||
APIKey: "",
|
||||
},
|
||||
{
|
||||
ModelName: "openrouter-auto",
|
||||
Model: "openrouter/auto",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ type Session struct {
|
|||
Key string `json:"key"`
|
||||
Messages []providers.Message `json:"messages"`
|
||||
Summary string `json:"summary,omitempty"`
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
Created time.Time `json:"created"`
|
||||
Updated time.Time `json:"updated"`
|
||||
}
|
||||
|
|
@ -145,6 +146,41 @@ func (sm *SessionManager) TruncateHistory(key string, keepLast int) {
|
|||
session.Updated = time.Now()
|
||||
}
|
||||
|
||||
func (sm *SessionManager) SetMetadata(key string, metaKey string, value any) {
|
||||
sm.mu.Lock()
|
||||
defer sm.mu.Unlock()
|
||||
|
||||
session, ok := sm.sessions[key]
|
||||
if !ok {
|
||||
session = &Session{
|
||||
Key: key,
|
||||
Messages: []providers.Message{},
|
||||
Metadata: make(map[string]any),
|
||||
Created: time.Now(),
|
||||
}
|
||||
sm.sessions[key] = session
|
||||
}
|
||||
|
||||
if session.Metadata == nil {
|
||||
session.Metadata = make(map[string]any)
|
||||
}
|
||||
session.Metadata[metaKey] = value
|
||||
session.Updated = time.Now()
|
||||
}
|
||||
|
||||
func (sm *SessionManager) GetMetadata(key string, metaKey string) (any, bool) {
|
||||
sm.mu.RLock()
|
||||
defer sm.mu.RUnlock()
|
||||
|
||||
session, ok := sm.sessions[key]
|
||||
if !ok || session.Metadata == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
val, ok := session.Metadata[metaKey]
|
||||
return val, ok
|
||||
}
|
||||
|
||||
// sanitizeFilename converts a session key into a cross-platform safe filename.
|
||||
// Session keys use "channel:chatID" (e.g. "telegram:123456") but ':' is the
|
||||
// volume separator on Windows, so filepath.Base would misinterpret the key.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue