From 2801f0a978176cc8579ee73ef84a39ceab90847b Mon Sep 17 00:00:00 2001 From: Duy /zuey/ Date: Sun, 5 Apr 2026 18:51:17 +0700 Subject: [PATCH] feat(providers): add OpenRouter identification headers (#705) * fix(ci): skip CI condition in semantic-release for main branch go-semantic-release auto-detects the default branch from GitHub API (which is dev), but releases are cut from main. The CI condition rejects runs on non-default branches. Use --no-ci to bypass this check since the workflow already gates on push to main. * docs: document CI/CD pipelines, release flow, and v2.66.0 changelog - CLAUDE.md: add CI/CD & Releases section with workflow table, tag patterns, Docker variants, beta/desktop release commands - CONTRIBUTING.md: expand Releases section with standard (auto), beta (manual tag), and desktop release workflows - docs/17-changelog.md: add v2.66.0 entry covering IDOR fix, BytePlus provider, per-agent grants, beta pipeline, and CI fixes * fix(telegram): handle group-to-supergroup migration seamlessly When a Telegram group upgrades to a supergroup, the chat ID changes and all existing references become stale. This caused send failures (400), orphaned sessions, and required manual re-pairing. Add dual-path migration handling: - Proactive: intercept inbound MigrateToChatID before isServiceMessage - Reactive: detect 400 + MigrateToChatID on send, migrate DB, retry DB migration updates in a single transaction (scoped by tenant + channel): - paired_devices: sender_id, chat_id - sessions: session_key, user_id - channel_contacts: sender_id - channel_pending_messages: history_key Also invalidates in-memory caches (approvedGroups, pairingReplySent, groupHistory) and handles media sends via migration retry in Send(). * feat(providers): add OpenRouter identification headers (#704) Add HTTP-Referer and X-Title headers to OpenRouter API requests for rankings and analytics visibility on openrouter.ai. --------- Co-authored-by: viettranx --- cmd/gateway_providers.go | 7 ++++++- internal/providers/openai.go | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/cmd/gateway_providers.go b/cmd/gateway_providers.go index 80702bfb..c4e365a9 100644 --- a/cmd/gateway_providers.go +++ b/cmd/gateway_providers.go @@ -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) diff --git a/internal/providers/openai.go b/internal/providers/openai.go index fcd057f4..8f4f4a56 100644 --- a/internal/providers/openai.go +++ b/internal/providers/openai.go @@ -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 {