service

package
v0.0.0-...-30b4b00 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 5, 2026 License: AGPL-3.0 Imports: 69 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// BillingSourceWallet 个人钱包(users.quota)
	BillingSourceWallet = "wallet"
	// BillingSourceEnterprise 企业钱包(enterprise_user.quota,由企业主钱包派发)
	BillingSourceEnterprise = "enterprise"
)
View Source
const (
	ViolationFeeCodePrefix     = "violation_fee."
	CSAMViolationMarker        = "Failed check: SAFETY_CHECK_TYPE"
	ContentViolatesUsageMarker = "Content violates usage guidelines"
)

Variables

View Source
var GetTaskAdaptorFunc func(platform constant.TaskPlatform) TaskPollingAdaptor

GetTaskAdaptorFunc 由 main 包注入,用于获取指定平台的任务适配器。 打破 service -> relay -> relay/channel -> service 的循环依赖。

Functions

func AcSearch

func AcSearch(findText string, dict []string, stopImmediately bool) (bool, []string)

func AppendChannelAffinityAdminInfo

func AppendChannelAffinityAdminInfo(c *gin.Context, adminInfo map[string]interface{})

func ApplyChannelAffinityOverrideTemplate

func ApplyChannelAffinityOverrideTemplate(c *gin.Context, paramOverride map[string]interface{}) (map[string]interface{}, bool)

ApplyChannelAffinityOverrideTemplate merges per-rule channel override templates onto the selected channel override config.

func BuildTieredTokenParams

func BuildTieredTokenParams(usage *dto.Usage, isClaudeUsageSemantic bool, usedVars map[string]bool) billingexpr.TokenParams

BuildTieredTokenParams constructs billingexpr.TokenParams from a dto.Usage, normalizing P and C so they mean "tokens not separately priced by the expression". Sub-categories (cache, image, audio) are only subtracted when the expression references them via their own variable.

GPT-format APIs report prompt_tokens / completion_tokens as totals that include all sub-categories (cache, image, audio). Claude-format APIs report them as text-only. This function normalizes to text-only when sub-categories are separately priced.

func CacheGetRandomSatisfiedChannel

func CacheGetRandomSatisfiedChannel(param *RetryParam) (*model.Channel, string, error)

CacheGetRandomSatisfiedChannel tries to get a random channel that satisfies the requirements. 尝试获取一个满足要求的随机渠道。

For "auto" tokenGroup with cross-group Retry enabled: 对于启用了跨分组重试的 "auto" tokenGroup:

  • Each group will exhaust all its priorities before moving to the next group. 每个分组会用完所有优先级后才会切换到下一个分组。

  • Uses ContextKeyAutoGroupIndex to track current group index. 使用 ContextKeyAutoGroupIndex 跟踪当前分组索引。

  • Uses ContextKeyAutoGroupRetryIndex to track the global Retry count when current group started. 使用 ContextKeyAutoGroupRetryIndex 跟踪当前分组开始时的全局重试次数。

  • priorityRetry = Retry - startRetryIndex, represents the priority level within current group. priorityRetry = Retry - startRetryIndex,表示当前分组内的优先级级别。

  • When GetRandomSatisfiedChannel returns nil (priorities exhausted), moves to next group. 当 GetRandomSatisfiedChannel 返回 nil(优先级用完)时,切换到下一个分组。

Example flow (2 groups, each with 2 priorities, RetryTimes=3): 示例流程(2个分组,每个有2个优先级,RetryTimes=3):

Retry=0: GroupA, priority0 (startRetryIndex=0, priorityRetry=0)
         分组A, 优先级0

Retry=1: GroupA, priority1 (startRetryIndex=0, priorityRetry=1)
         分组A, 优先级1

Retry=2: GroupA exhausted → GroupB, priority0 (startRetryIndex=2, priorityRetry=0)
         分组A用完 → 分组B, 优先级0

Retry=3: GroupB, priority1 (startRetryIndex=2, priorityRetry=1)
         分组B, 优先级1

func CalcOpenRouterCacheCreateTokens

func CalcOpenRouterCacheCreateTokens(usage dto.Usage, priceData types.PriceData) int

func ChargeViolationFeeIfNeeded

func ChargeViolationFeeIfNeeded(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, apiErr *types.FastTokenError) bool

ChargeViolationFeeIfNeeded charges an additional fee after the normal flow finishes (including refund). It uses Grok fee settings as the fee policy.

func ChatCompletionsRequestToResponsesRequest

func ChatCompletionsRequestToResponsesRequest(req *dto.GeneralOpenAIRequest) (*dto.OpenAIResponsesRequest, error)

func CheckNotificationLimit

func CheckNotificationLimit(userId int, notifyType string) (bool, error)

CheckNotificationLimit checks if the user has exceeded their notification limit Returns true if the user can send notification, false if limit exceeded

func CheckSensitiveMessages

func CheckSensitiveMessages(messages []dto.Message) ([]string, error)

func CheckSensitiveText

func CheckSensitiveText(text string) (bool, []string)

func ClaudeErrorWrapper

func ClaudeErrorWrapper(err error, code string, statusCode int) *dto.ClaudeErrorWithStatusCode

func ClaudeErrorWrapperLocal

func ClaudeErrorWrapperLocal(err error, code string, statusCode int) *dto.ClaudeErrorWithStatusCode

func ClaudeToOpenAIRequest

func ClaudeToOpenAIRequest(claudeRequest dto.ClaudeRequest, info *relaycommon.RelayInfo) (*dto.GeneralOpenAIRequest, error)

func CleanupFileSources

func CleanupFileSources(c *gin.Context)

CleanupFileSources 清理请求中所有注册的 FileSource 应在请求结束时调用(通常由中间件自动调用)

func ClearChannelAffinityCacheAll

func ClearChannelAffinityCacheAll() int

func ClearChannelAffinityCacheByRuleName

func ClearChannelAffinityCacheByRuleName(ruleName string) (int, error)

func CloseResponseBodyGracefully

func CloseResponseBodyGracefully(httpResponse *http.Response)

func ConvertSimpleChangeParams

func ConvertSimpleChangeParams(content string) *dto.MidjourneyRequest

func CountAudioTokenInput

func CountAudioTokenInput(audioBase64 string, audioFormat string) (int, error)

func CountAudioTokenOutput

func CountAudioTokenOutput(audioBase64 string, audioFormat string) (int, error)

func CountTextToken

func CountTextToken(text string, model string) int

CountTextToken 统计文本的token数量,仅OpenAI模型使用tokenizer,其余模型使用估算

func CountTokenInput

func CountTokenInput(input any, model string) int

func CountTokenRealtime

func CountTokenRealtime(info *relaycommon.RelayInfo, request dto.RealtimeEvent, model string) (int, int, error)

func CoverPlusActionToNormalAction

func CoverPlusActionToNormalAction(midjRequest *dto.MidjourneyRequest) *dto.MidjourneyResponse

func CoverTaskActionToModelName

func CoverTaskActionToModelName(platform constant.TaskPlatform, action string) string

func CovertMjpActionToModelName

func CovertMjpActionToModelName(mjAction string) string

func DecodeBase64AudioData

func DecodeBase64AudioData(audioBase64 string) (string, error)

func DecodeBase64FileData

func DecodeBase64FileData(base64String string) (string, string, error)

func DecodeBase64ImageData

func DecodeBase64ImageData(base64String string) (image.Config, string, string, error)

return image.Config, format, clean base64 string, error

func DecodeUrlImageData

func DecodeUrlImageData(imageUrl string) (image.Config, string, error)

func DetectFileType

func DetectFileType(mimeType string) types.FileType

DetectFileType 检测文件类型

func DisableChannel

func DisableChannel(channelError types.ChannelError, reason string)

disable & notify

func DispatchPlatformUpdate

func DispatchPlatformUpdate(platform constant.TaskPlatform, taskChannelM map[int][]string, taskM map[string]*model.Task)

DispatchPlatformUpdate 按平台分发轮询更新

func DoDownloadRequest

func DoDownloadRequest(originUrl string, reason ...string) (resp *http.Response, err error)

func DoMidjourneyHttpRequest

func DoMidjourneyHttpRequest(c *gin.Context, timeout time.Duration, fullRequestURL string) (*dto.MidjourneyResponseWithStatusCode, []byte, error)

func DoWorkerRequest

func DoWorkerRequest(req *WorkerRequest) (*http.Response, error)

DoWorkerRequest 通过Worker发送请求

func EnableChannel

func EnableChannel(channelId int, usingKey string, channelName string)

func EstimateRequestToken

func EstimateRequestToken(c *gin.Context, meta *types.TokenCountMeta, info *relaycommon.RelayInfo) (int, error)

func EstimateToken

func EstimateToken(provider Provider, text string) int

EstimateToken 计算 Token 数量

func EstimateTokenByModel

func EstimateTokenByModel(model, text string) int

func ExtractCodexAccountIDFromJWT

func ExtractCodexAccountIDFromJWT(token string) (string, bool)

func ExtractEmailFromJWT

func ExtractEmailFromJWT(token string) (string, bool)

func ExtractOutputTextFromResponses

func ExtractOutputTextFromResponses(resp *dto.OpenAIResponsesResponse) string

func FetchCodexWhamUsage

func FetchCodexWhamUsage(
	ctx context.Context,
	client *http.Client,
	baseURL string,
	accessToken string,
	accountID string,
) (statusCode int, body []byte, err error)

func FindOrCreateOAuthUser

func FindOrCreateOAuthUser(provider oauth.Provider, oauthUser *oauth.OAuthUser) (*model.User, error)

FindOrCreateOAuthUser finds an existing user by OAuth provider ID, migrates legacy IDs when applicable, or creates a new user if registration is enabled.

Returns the user model. On error, returns one of:

  • *OAuthUserDeletedError — user existed but was deleted
  • *OAuthRegistrationDisabledError — user not found and registration is off
  • generic error — database or other failure

func GeminiToOpenAIRequest

func GeminiToOpenAIRequest(geminiRequest *dto.GeminiChatRequest, info *relaycommon.RelayInfo) (*dto.GeneralOpenAIRequest, error)

func GenerateAudioOtherInfo

func GenerateAudioOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage *dto.Usage, modelRatio, groupRatio, completionRatio, audioRatio, audioCompletionRatio, modelPrice, userGroupRatio float64) map[string]interface{}

func GenerateClaudeOtherInfo

func GenerateClaudeOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, modelRatio, groupRatio, completionRatio float64,
	cacheTokens int, cacheRatio float64,
	cacheCreationTokens int, cacheCreationRatio float64,
	cacheCreationTokens5m int, cacheCreationRatio5m float64,
	cacheCreationTokens1h int, cacheCreationRatio1h float64,
	modelPrice float64, userGroupRatio float64) map[string]interface{}

func GenerateMjOtherInfo

func GenerateMjOtherInfo(relayInfo *relaycommon.RelayInfo, priceData types.PriceData) map[string]interface{}

func GenerateTextOtherInfo

func GenerateTextOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, modelRatio, groupRatio, completionRatio float64,
	cacheTokens int, cacheRatio float64, modelPrice float64, userGroupRatio float64) map[string]interface{}

func GenerateWssOtherInfo

func GenerateWssOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage *dto.RealtimeUsage, modelRatio, groupRatio, completionRatio, audioRatio, audioCompletionRatio, modelPrice, userGroupRatio float64) map[string]interface{}

func GetBase64Data

func GetBase64Data(c *gin.Context, source types.FileSource, reason ...string) (string, string, error)

GetBase64Data 获取 base64 编码的数据

func GetCallbackAddress

func GetCallbackAddress() string

func GetFileBase64FromUrl

func GetFileBase64FromUrl(c *gin.Context, url string, reason ...string) (*types.LocalFileData, error)

GetFileBase64FromUrl 从 URL 获取文件的 base64 编码数据 Deprecated: 请使用 GetBase64Data 配合 types.NewURLFileSource 替代 此函数保留用于向后兼容,内部已重构为调用统一的文件服务

func GetFileTypeFromUrl

func GetFileTypeFromUrl(c *gin.Context, url string, reason ...string) (string, error)

GetFileTypeFromUrl 获取文件类型,返回 mime type, 例如 image/jpeg, image/png, image/gif, image/bmp, image/tiff, application/pdf 如果获取失败,返回 application/octet-stream

func GetHttpClient

func GetHttpClient() *http.Client

func GetHttpClientWithProxy

func GetHttpClientWithProxy(proxyURL string) (*http.Client, error)

GetHttpClientWithProxy returns the default client or a proxy-enabled one when proxyURL is provided.

func GetImageConfig

func GetImageConfig(c *gin.Context, source types.FileSource) (image.Config, string, error)

GetImageConfig 获取图片配置

func GetImageFromUrl

func GetImageFromUrl(url string) (mimeType string, data string, err error)

GetImageFromUrl 获取图片的类型和base64编码的数据

func GetMimeType

func GetMimeType(c *gin.Context, source types.FileSource) (string, error)

GetMimeType 获取文件的 MIME 类型

func GetMimeTypeByExtension

func GetMimeTypeByExtension(ext string) string

func GetMjRequestModel

func GetMjRequestModel(relayMode int, midjRequest *dto.MidjourneyRequest) (string, *dto.MidjourneyResponse, bool)

func GetPreferredChannelByAffinity

func GetPreferredChannelByAffinity(c *gin.Context, modelName string, usingGroup string) (int, bool)

func GetUserAutoGroup

func GetUserAutoGroup(userGroup string) []string

GetUserAutoGroup 根据用户分组获取自动分组设置

func GetUserGroupRatio

func GetUserGroupRatio(userGroup, group string) float64

GetUserGroupRatio 获取用户使用某个分组的倍率 userGroup 用户分组 group 需要获取倍率的分组

func GetUserUsableGroups

func GetUserUsableGroups(userGroup string) map[string]string

func GroupInUserUsableGroups

func GroupInUserUsableGroups(userGroup, groupName string) bool

func HasCSAMViolationMarker

func HasCSAMViolationMarker(err *types.FastTokenError) bool

func IOCopyBytesGracefully

func IOCopyBytesGracefully(c *gin.Context, src *http.Response, data []byte)

func InitAc

func InitAc(dict []string) *stubMachine

func InitHttpClient

func InitHttpClient()

func InitSMSService

func InitSMSService(config SMSConfig)

InitSMSService 初始化短信服务

func InitTokenEncoders

func InitTokenEncoders()

func InjectTieredBillingInfo

func InjectTieredBillingInfo(other map[string]interface{}, relayInfo *relaycommon.RelayInfo, result *billingexpr.TieredResult)

InjectTieredBillingInfo overlays tiered billing fields onto an existing module-specific other map. Call this after GenerateTextOtherInfo / GenerateClaudeOtherInfo / etc. when the request used tiered_expr billing.

func IsViolationFeeCode

func IsViolationFeeCode(code types.ErrorCode) bool

func LoadFileSource

func LoadFileSource(c *gin.Context, source types.FileSource, reason ...string) (*types.CachedFileData, error)

LoadFileSource 加载文件源数据 这是统一的入口,会自动处理缓存和不同的来源类型

func LogTaskConsumption

func LogTaskConsumption(c *gin.Context, info *relaycommon.RelayInfo)

LogTaskConsumption 记录任务消费日志和统计信息(仅记录,不涉及实际扣费)。 实际扣费已由 BillingSession(PreConsumeBilling + SettleBilling)完成。

func MarkChannelAffinityUsed

func MarkChannelAffinityUsed(c *gin.Context, selectedGroup string, channelID int)

func MidjourneyErrorWithStatusCodeWrapper

func MidjourneyErrorWithStatusCodeWrapper(code int, desc string, statusCode int) *dto.MidjourneyResponseWithStatusCode

func MidjourneyErrorWrapper

func MidjourneyErrorWrapper(code int, desc string) *dto.MidjourneyResponse

func NewProxyHttpClient

func NewProxyHttpClient(proxyURL string) (*http.Client, error)

NewProxyHttpClient 创建支持代理的 HTTP 客户端

func NormalizeCacheCreationSplit

func NormalizeCacheCreationSplit(totalTokens int, tokens5m int, tokens1h int) (int, int)

func NormalizeViolationFeeError

func NormalizeViolationFeeError(err *types.FastTokenError) *types.FastTokenError

NormalizeViolationFeeError ensures: - if the CSAM marker is present, error.code is set to a stable violation-fee code and skip-retry is enabled. - if error.code already has the violation-fee prefix, skip-retry is enabled.

It must be called before retry decision logic.

func NotifyRootUser

func NotifyRootUser(t string, subject string, content string)

func NotifyUpstreamModelUpdateWatchers

func NotifyUpstreamModelUpdateWatchers(subject string, content string)

func NotifyUser

func NotifyUser(userId int, userEmail string, userSetting dto.UserSetting, data dto.Notify) error

func ObserveChannelAffinityUsageCacheByRelayFormat

func ObserveChannelAffinityUsageCacheByRelayFormat(c *gin.Context, usage *dto.Usage, relayFormat types.RelayFormat)

ObserveChannelAffinityUsageCacheByRelayFormat records usage cache stats with a stable rate mode derived from relay format.

func ObserveChannelAffinityUsageCacheFromContext

func ObserveChannelAffinityUsageCacheFromContext(c *gin.Context, usage *dto.Usage, cachedTokenRateMode string)

func PaymentReturnURL

func PaymentReturnURL(suffix string) string

func PostAudioConsumeQuota

func PostAudioConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage *dto.Usage, extraContent string)

func PostConsumeQuota

func PostConsumeQuota(relayInfo *relaycommon.RelayInfo, quota int, preConsumedQuota int, sendEmail bool) (err error)

func PostTextConsumeQuota

func PostTextConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage *dto.Usage, extraContent []string)

func PostWssConsumeQuota

func PostWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, modelName string,
	usage *dto.RealtimeUsage, extraContent string)

func PreConsumeBilling

func PreConsumeBilling(c *gin.Context, preConsumedQuota int, relayInfo *relaycommon.RelayInfo) *types.FastTokenError

PreConsumeBilling 根据用户计费偏好创建 BillingSession 并执行预扣费。 会话存储在 relayInfo.Billing 上,供后续 Settle / Refund 使用。

func PreConsumeTokenQuota

func PreConsumeTokenQuota(relayInfo *relaycommon.RelayInfo, quota int) error

func PreWssConsumeQuota

func PreWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage *dto.RealtimeUsage) error

func RecalculateTaskQuota

func RecalculateTaskQuota(ctx context.Context, task *model.Task, actualQuota int, reason string)

RecalculateTaskQuota 通用的异步差额结算。 actualQuota 是任务完成后的实际应扣额度,与预扣额度 (task.Quota) 做差额结算。 reason 用于日志记录(例如 "token重算" 或 "adaptor调整")。

func RecalculateTaskQuotaByTokens

func RecalculateTaskQuotaByTokens(ctx context.Context, task *model.Task, totalTokens int)

RecalculateTaskQuotaByTokens 根据实际 token 消耗重新计费(异步差额结算)。 当任务成功且返回了 totalTokens 时,根据模型倍率和分组倍率重新计算实际扣费额度, 与预扣费的差额进行补扣或退还。支持钱包和订阅计费来源。

func RecordChannelAffinity

func RecordChannelAffinity(c *gin.Context, channelID int)

func RefundTaskQuota

func RefundTaskQuota(ctx context.Context, task *model.Task, reason string)

RefundTaskQuota 统一的任务失败退款逻辑。 当异步任务失败时,将预扣的 quota 退还给用户(支持钱包和订阅),并退还令牌额度。

func RelayErrorHandler

func RelayErrorHandler(ctx context.Context, resp *http.Response, showBodyWhenFail bool) (FastTokenErr *types.FastTokenError)

func RemoveDuplicate

func RemoveDuplicate(s []string) []string

func ResetProxyClientCache

func ResetProxyClientCache()

ResetProxyClientCache 清空代理客户端缓存,确保下次使用时重新初始化

func ResetStatusCode

func ResetStatusCode(FastTokenErr *types.FastTokenError, statusCodeMappingStr string)

func ResolveFundingForLegacyPath

func ResolveFundingForLegacyPath(relayInfo *relaycommon.RelayInfo, needQuota int) (int, error)

ResolveFundingForLegacyPath 为不经过 BillingSession 的路径(Midjourney 等)锁定资金来源。 选路规则与 CompositeFunding 一致:企业余额足够本次消费则走企业钱包,否则走个人钱包。 结果写回 relayInfo.BillingSource / EnterpriseUserId,返回选中来源的可用余额。

func ResponseOpenAI2Claude

func ResponseOpenAI2Claude(openAIResponse *dto.OpenAITextResponse, info *relaycommon.RelayInfo) *dto.ClaudeResponse

func ResponseOpenAI2Gemini

func ResponseOpenAI2Gemini(openAIResponse *dto.OpenAITextResponse, info *relaycommon.RelayInfo) *dto.GeminiChatResponse

ResponseOpenAI2Gemini 将 OpenAI 响应转换为 Gemini 格式

func ResponseText2Usage

func ResponseText2Usage(c *gin.Context, responseText string, modeName string, promptTokens int) *dto.Usage

func ResponsesResponseToChatCompletionsResponse

func ResponsesResponseToChatCompletionsResponse(resp *dto.OpenAIResponsesResponse, id string) (*dto.OpenAITextResponse, *dto.Usage, error)

func SendVerificationCode

func SendVerificationCode(phone string, purpose model.SMSPurpose) (string, error)

SendVerificationCode 发送验证码(统一入口) 先调 SMS 服务商发送,发送成功后再保存到数据库,避免服务商返回失败时 DB 产生孤儿记录

func SendWebhookNotify

func SendWebhookNotify(webhookURL string, secret string, data dto.Notify) error

SendWebhookNotify 发送 webhook 通知

func SensitiveWordContains

func SensitiveWordContains(text string) (bool, []string)

SensitiveWordContains 是否包含敏感词,返回是否包含敏感词和敏感词列表

func SensitiveWordReplace

func SensitiveWordReplace(text string, returnImmediately bool) (bool, []string, string)

SensitiveWordReplace 敏感词替换,返回是否包含敏感词和替换后的文本

func SettleBilling

func SettleBilling(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, actualQuota int) error

SettleBilling 执行计费结算。如果 RelayInfo 上有 BillingSession 则通过 session 结算, 否则回退到旧的 PostConsumeQuota 路径(兼容按次计费等场景)。

func ShouldChatCompletionsUseResponsesGlobal

func ShouldChatCompletionsUseResponsesGlobal(channelID int, channelType int, model string) bool

func ShouldChatCompletionsUseResponsesPolicy

func ShouldChatCompletionsUseResponsesPolicy(policy model_setting.ChatCompletionsToResponsesPolicy, channelID int, channelType int, model string) bool

func ShouldCopyUpstreamHeader

func ShouldCopyUpstreamHeader(c *gin.Context, k string, v []string) bool

ShouldCopyUpstreamHeader checks whether a given upstream response header should be copied to the client response. It returns false for Content-Length (managed separately) and X-Oneapi-Request-Id (to preserve the local instance ID). When the upstream header is X-Oneapi-Request-Id, the value is captured into the Gin context for later logging.

func ShouldDisableChannel

func ShouldDisableChannel(err *types.FastTokenError) bool

func ShouldEnableChannel

func ShouldEnableChannel(FastTokenError *types.FastTokenError, status int) bool

func ShouldSkipRetryAfterChannelAffinityFailure

func ShouldSkipRetryAfterChannelAffinityFailure(c *gin.Context) bool

func StartCodexCredentialAutoRefreshTask

func StartCodexCredentialAutoRefreshTask()

func StreamResponseOpenAI2Claude

func StreamResponseOpenAI2Claude(openAIResponse *dto.ChatCompletionsStreamResponse, info *relaycommon.RelayInfo) []*dto.ClaudeResponse

func StreamResponseOpenAI2Gemini

func StreamResponseOpenAI2Gemini(openAIResponse *dto.ChatCompletionsStreamResponse, info *relaycommon.RelayInfo) *dto.GeminiChatResponse

StreamResponseOpenAI2Gemini 将 OpenAI 流式响应转换为 Gemini 格式

func SundaySearch

func SundaySearch(text string, pattern string) bool

func TaskErrorFromAPIError

func TaskErrorFromAPIError(apiErr *types.FastTokenError) *dto.TaskError

TaskErrorFromAPIError 将 PreConsumeBilling 返回的 FastTokenError 转换为 TaskError。

func TaskErrorWrapper

func TaskErrorWrapper(err error, code string, statusCode int) *dto.TaskError

func TaskErrorWrapperLocal

func TaskErrorWrapperLocal(err error, code string, statusCode int) *dto.TaskError

func TaskPollingLoop

func TaskPollingLoop()

TaskPollingLoop 主轮询循环,每 15 秒检查一次未完成的任务

func TriggerWebhook

func TriggerWebhook(userId int, event string, data map[string]interface{})

TriggerWebhook fires webhooks for a user that match the given event. Each matching webhook is sent in a separate goroutine (fire and forget).

func TryTieredSettle

func TryTieredSettle(relayInfo *relaycommon.RelayInfo, params billingexpr.TokenParams) (ok bool, quota int, result *billingexpr.TieredResult)

TryTieredSettle checks if the request uses tiered_expr billing and, if so, computes the actual quota using the frozen BillingSnapshot. Returns:

  • ok=true, quota, result when tiered billing applies
  • ok=false, 0, nil when it doesn't (caller should fall through to existing logic)

func UpdateSunoTasks

func UpdateSunoTasks(ctx context.Context, taskChannelM map[int][]string, taskM map[string]*model.Task) error

UpdateSunoTasks 按渠道更新所有 Suno 任务

func UpdateVideoTasks

func UpdateVideoTasks(ctx context.Context, platform constant.TaskPlatform, taskChannelM map[int][]string, taskM map[string]*model.Task) error

UpdateVideoTasks 按渠道更新所有视频任务

func ValidUsage

func ValidUsage(usage *dto.Usage) bool

func VerifyPhoneCode

func VerifyPhoneCode(phone string, code string, purpose model.SMSPurpose) (bool, error)

VerifyPhoneCode 验证手机验证码

func WrapAsViolationFeeGrokCSAM

func WrapAsViolationFeeGrokCSAM(err *types.FastTokenError) *types.FastTokenError

Types

type AliyunSMSService

type AliyunSMSService struct {
	// contains filtered or unexported fields
}

AliyunSMSService 阿里云短信服务实现

func NewAliyunSMSService

func NewAliyunSMSService(config SMSConfig) *AliyunSMSService

func (*AliyunSMSService) SendSMS

func (s *AliyunSMSService) SendSMS(phone string, code string, purpose model.SMSPurpose) error

type BillingSession

type BillingSession struct {
	// contains filtered or unexported fields
}

BillingSession 封装单次请求的预扣费/结算/退款生命周期。 实现 relaycommon.BillingSettler 接口。

func NewBillingSession

func NewBillingSession(c *gin.Context, relayInfo *relaycommon.RelayInfo, preConsumedQuota int) (*BillingSession, *types.FastTokenError)

NewBillingSession 创建基于钱包的 BillingSession。

func (*BillingSession) GetPreConsumedQuota

func (s *BillingSession) GetPreConsumedQuota() int

GetPreConsumedQuota 返回实际预扣的额度。

func (*BillingSession) NeedsRefund

func (s *BillingSession) NeedsRefund() bool

NeedsRefund 返回是否存在需要退还的预扣状态。

func (*BillingSession) Refund

func (s *BillingSession) Refund(c *gin.Context)

Refund 退还所有预扣费,幂等安全,异步执行。

func (*BillingSession) Reserve

func (s *BillingSession) Reserve(targetQuota int) error

func (*BillingSession) Settle

func (s *BillingSession) Settle(actualQuota int) error

Settle 根据实际消耗额度进行结算。 资金来源和令牌额度分两步提交:若资金来源已提交但令牌调整失败, 会标记 fundingSettled 防止 Refund 对已提交的资金来源执行退款。

type ChannelAffinityCacheStats

type ChannelAffinityCacheStats struct {
	Enabled       bool           `json:"enabled"`
	Total         int            `json:"total"`
	Unknown       int            `json:"unknown"`
	ByRuleName    map[string]int `json:"by_rule_name"`
	CacheCapacity int            `json:"cache_capacity"`
	CacheAlgo     string         `json:"cache_algo"`
}

func GetChannelAffinityCacheStats

func GetChannelAffinityCacheStats() ChannelAffinityCacheStats

type ChannelAffinityStatsContext

type ChannelAffinityStatsContext struct {
	RuleName       string
	UsingGroup     string
	KeyFingerprint string
	TTLSeconds     int64
}

func GetChannelAffinityStatsContext

func GetChannelAffinityStatsContext(c *gin.Context) (ChannelAffinityStatsContext, bool)

type ChannelAffinityUsageCacheCounters

type ChannelAffinityUsageCacheCounters struct {
	CachedTokenRateMode string `json:"cached_token_rate_mode"`

	Hit           int64 `json:"hit"`
	Total         int64 `json:"total"`
	WindowSeconds int64 `json:"window_seconds"`

	PromptTokens         int64 `json:"prompt_tokens"`
	CompletionTokens     int64 `json:"completion_tokens"`
	TotalTokens          int64 `json:"total_tokens"`
	CachedTokens         int64 `json:"cached_tokens"`
	PromptCacheHitTokens int64 `json:"prompt_cache_hit_tokens"`
	LastSeenAt           int64 `json:"last_seen_at"`
}

type ChannelAffinityUsageCacheStats

type ChannelAffinityUsageCacheStats struct {
	RuleName            string `json:"rule_name"`
	UsingGroup          string `json:"using_group"`
	KeyFingerprint      string `json:"key_fp"`
	CachedTokenRateMode string `json:"cached_token_rate_mode"`

	Hit           int64 `json:"hit"`
	Total         int64 `json:"total"`
	WindowSeconds int64 `json:"window_seconds"`

	PromptTokens         int64 `json:"prompt_tokens"`
	CompletionTokens     int64 `json:"completion_tokens"`
	TotalTokens          int64 `json:"total_tokens"`
	CachedTokens         int64 `json:"cached_tokens"`
	PromptCacheHitTokens int64 `json:"prompt_cache_hit_tokens"`
	LastSeenAt           int64 `json:"last_seen_at"`
}

func GetChannelAffinityUsageCacheStats

func GetChannelAffinityUsageCacheStats(ruleName, usingGroup, keyFp string) ChannelAffinityUsageCacheStats

type CodexCredentialRefreshOptions

type CodexCredentialRefreshOptions struct {
	ResetCaches bool
}

type CodexOAuthAuthorizationFlow

type CodexOAuthAuthorizationFlow struct {
	State        string
	Verifier     string
	Challenge    string
	AuthorizeURL string
}

func CreateCodexOAuthAuthorizationFlow

func CreateCodexOAuthAuthorizationFlow() (*CodexOAuthAuthorizationFlow, error)

type CodexOAuthKey

type CodexOAuthKey struct {
	IDToken      string `json:"id_token,omitempty"`
	AccessToken  string `json:"access_token,omitempty"`
	RefreshToken string `json:"refresh_token,omitempty"`

	AccountID   string `json:"account_id,omitempty"`
	LastRefresh string `json:"last_refresh,omitempty"`
	Email       string `json:"email,omitempty"`
	Type        string `json:"type,omitempty"`
	Expired     string `json:"expired,omitempty"`
}

func RefreshCodexChannelCredential

func RefreshCodexChannelCredential(ctx context.Context, channelID int, opts CodexCredentialRefreshOptions) (*CodexOAuthKey, *model.Channel, error)

type CodexOAuthTokenResult

type CodexOAuthTokenResult struct {
	AccessToken  string
	RefreshToken string
	ExpiresAt    time.Time
}

func ExchangeCodexAuthorizationCode

func ExchangeCodexAuthorizationCode(ctx context.Context, code string, verifier string) (*CodexOAuthTokenResult, error)

func ExchangeCodexAuthorizationCodeWithProxy

func ExchangeCodexAuthorizationCodeWithProxy(ctx context.Context, code string, verifier string, proxyURL string) (*CodexOAuthTokenResult, error)

func RefreshCodexOAuthToken

func RefreshCodexOAuthToken(ctx context.Context, refreshToken string) (*CodexOAuthTokenResult, error)

func RefreshCodexOAuthTokenWithProxy

func RefreshCodexOAuthTokenWithProxy(ctx context.Context, refreshToken string, proxyURL string) (*CodexOAuthTokenResult, error)

type CompositeFunding

type CompositeFunding struct {
	// contains filtered or unexported fields
}

CompositeFunding 在预扣阶段一次性选定资金来源,之后所有操作都锁定在同一来源上。 选路规则(用户拍板):

  1. 用户是 active 企业成员且企业余额足够本次预扣 → 走企业钱包
  2. 否则 → 走个人钱包

单次请求内绝不跨钱包拆分,退款一律原路返回。

func NewCompositeFunding

func NewCompositeFunding(userId int, membership *model.EnterpriseMembership) *CompositeFunding

NewCompositeFunding 构造资金来源。membership 为 nil 表示用户不属于任何企业。

func (*CompositeFunding) ActiveEnterpriseUserId

func (c *CompositeFunding) ActiveEnterpriseUserId() int

ActiveEnterpriseUserId 返回本次请求实际选中的 enterprise_user.id。 未选中企业钱包时返回 0。异步任务据此把退款原路退回企业钱包。

func (*CompositeFunding) EnterpriseId

func (c *CompositeFunding) EnterpriseId() int

EnterpriseId 返回绑定的企业 ID,无企业身份时返回 0

func (*CompositeFunding) HasEnterprise

func (c *CompositeFunding) HasEnterprise() bool

HasEnterprise 返回用户是否具备企业资金来源

func (*CompositeFunding) PreConsume

func (c *CompositeFunding) PreConsume(amount int) error

func (*CompositeFunding) Refund

func (c *CompositeFunding) Refund() error

func (*CompositeFunding) Reserve

func (c *CompositeFunding) Reserve(delta int) error

func (*CompositeFunding) RollbackReserve

func (c *CompositeFunding) RollbackReserve(delta int)

func (*CompositeFunding) Settle

func (c *CompositeFunding) Settle(delta int) error

func (*CompositeFunding) Source

func (c *CompositeFunding) Source() string

type EnterpriseFunding

type EnterpriseFunding struct {
	// contains filtered or unexported fields
}

EnterpriseFunding 从企业派发给成员的余额中扣费。 enterprise_user.quota 为成员真实可用余额,used_quota 由 model 层同步维护。

func (*EnterpriseFunding) PreConsume

func (e *EnterpriseFunding) PreConsume(amount int) error

func (*EnterpriseFunding) Refund

func (e *EnterpriseFunding) Refund() error

func (*EnterpriseFunding) Reserve

func (e *EnterpriseFunding) Reserve(delta int) error

func (*EnterpriseFunding) RollbackReserve

func (e *EnterpriseFunding) RollbackReserve(delta int)

func (*EnterpriseFunding) Settle

func (e *EnterpriseFunding) Settle(delta int) error

func (*EnterpriseFunding) Source

func (e *EnterpriseFunding) Source() string

type FundingSource

type FundingSource interface {
	// Source 返回资金来源标识:"wallet" | "enterprise"
	Source() string
	// PreConsume 从该资金来源预扣 amount 额度
	PreConsume(amount int) error
	// Settle 根据差额调整资金来源(正数补扣,负数退还)
	Settle(delta int) error
	// Refund 退还所有预扣费
	Refund() error
	// Reserve 在已有预扣基础上追加预扣 delta(流式请求中途补扣)
	Reserve(delta int) error
	// RollbackReserve 回滚一次失败的 Reserve
	RollbackReserve(delta int)
}

FundingSource 抽象了预扣费的资金来源。 实现者必须保证 PreConsume / Settle / Refund / Reserve 之间的额度守恒。

type ModelHistoryModel

type ModelHistoryModel struct {
	Name   string `json:"name"`
	Vendor string `json:"vendor"`
	Total  int64  `json:"total"`
}

type ModelHistoryPoint

type ModelHistoryPoint struct {
	Ts     string `json:"ts"`
	Label  string `json:"label"`
	Model  string `json:"model"`
	Vendor string `json:"vendor"`
	Tokens int64  `json:"tokens"`
}

type ModelHistorySeries

type ModelHistorySeries struct {
	Points  []ModelHistoryPoint `json:"points"`
	Models  []ModelHistoryModel `json:"models"`
	Buckets int                 `json:"buckets"`
}

type OAuthRegistrationDisabledError

type OAuthRegistrationDisabledError struct{}

OAuthRegistrationDisabledError is returned when registration is disabled and the OAuth user does not exist.

func (*OAuthRegistrationDisabledError) Error

type OAuthUserDeletedError

type OAuthUserDeletedError struct{}

OAuthUserDeletedError is returned when an OAuth user was found but has been deleted.

func (*OAuthUserDeletedError) Error

func (e *OAuthUserDeletedError) Error() string

type PlaceholderSMSService

type PlaceholderSMSService struct{}

PlaceholderSMSService 占位短信服务(仅日志输出,不实际发送)

func (*PlaceholderSMSService) SendSMS

func (s *PlaceholderSMSService) SendSMS(phone string, code string, purpose model.SMSPurpose) error

type PreflightResult

type PreflightResult struct {
	Meta      *types.TokenCountMeta
	Tokens    int
	PriceData types.PriceData
}

PreflightResult holds the results of the relay pre-flight pipeline. All fields are valid when the returned FastTokenError is nil.

func RunRelayPreflight

func RunRelayPreflight(c *gin.Context, relayInfo *relaycommon.RelayInfo, request dto.Request) (*PreflightResult, *types.FastTokenError)

RunRelayPreflight executes the relay pre-flight pipeline:

  1. Build token count metadata
  2. Sensitive content check (when enabled)
  3. Token estimation
  4. Price calculation

On error, returns the appropriate FastTokenError that the controller can pass directly to the response formatter.

type Provider

type Provider string

Provider 定义模型厂商大类

const (
	OpenAI  Provider = "openai"  // 代表 GPT-3.5, GPT-4, GPT-4o
	Gemini  Provider = "gemini"  // 代表 Gemini 1.0, 1.5 Pro/Flash
	Claude  Provider = "claude"  // 代表 Claude 3, 3.5 Sonnet
	Unknown Provider = "unknown" // 兜底默认
)

type QuotaInfo

type QuotaInfo struct {
	InputDetails  TokenDetails
	OutputDetails TokenDetails
	ModelName     string
	UsePrice      bool
	ModelPrice    float64
	ModelRatio    float64
	GroupRatio    float64
}

type RankedModel

type RankedModel struct {
	Rank         int     `json:"rank"`
	PreviousRank *int    `json:"previous_rank,omitempty"`
	ModelName    string  `json:"model_name"`
	Vendor       string  `json:"vendor"`
	VendorIcon   string  `json:"vendor_icon,omitempty"`
	Category     string  `json:"category"`
	TotalTokens  int64   `json:"total_tokens"`
	Share        float64 `json:"share"`
	GrowthPct    float64 `json:"growth_pct"`
}

type RankedVendor

type RankedVendor struct {
	Rank        int     `json:"rank"`
	Vendor      string  `json:"vendor"`
	VendorIcon  string  `json:"vendor_icon,omitempty"`
	TotalTokens int64   `json:"total_tokens"`
	Share       float64 `json:"share"`
	GrowthPct   float64 `json:"growth_pct"`
	ModelsCount int     `json:"models_count"`
	TopModel    string  `json:"top_model"`
}

type RankingMover

type RankingMover struct {
	ModelName   string  `json:"model_name"`
	Vendor      string  `json:"vendor"`
	VendorIcon  string  `json:"vendor_icon,omitempty"`
	RankDelta   int     `json:"rank_delta"`
	CurrentRank int     `json:"current_rank"`
	GrowthPct   float64 `json:"growth_pct"`
}

type RankingsResponse

type RankingsResponse struct {
	Models             []RankedModel      `json:"models"`
	Vendors            []RankedVendor     `json:"vendors"`
	TopMovers          []RankingMover     `json:"top_movers"`
	TopDroppers        []RankingMover     `json:"top_droppers"`
	ModelsHistory      ModelHistorySeries `json:"models_history"`
	VendorShareHistory VendorShareSeries  `json:"vendor_share_history"`
}

func GetRankingsSnapshot

func GetRankingsSnapshot(period string) (*RankingsResponse, error)

type RetryParam

type RetryParam struct {
	Ctx        *gin.Context
	TokenGroup string
	ModelName  string
	Retry      *int
	// contains filtered or unexported fields
}

func (*RetryParam) GetRetry

func (p *RetryParam) GetRetry() int

func (*RetryParam) IncreaseRetry

func (p *RetryParam) IncreaseRetry()

func (*RetryParam) ResetRetryNextTry

func (p *RetryParam) ResetRetryNextTry()

func (*RetryParam) SetRetry

func (p *RetryParam) SetRetry(retry int)

type SMSConfig

type SMSConfig struct {
	Provider   string // "tencent" | "aliyun" | "placeholder"
	SecretId   string
	SecretKey  string
	TemplateId string
	SignName   string
}

SMSConfig 短信服务配置

type SMSService

type SMSService interface {
	SendSMS(phone string, code string, purpose model.SMSPurpose) error
}

SMSService 短信服务接口

func GetSMSService

func GetSMSService() SMSService

GetSMSService 获取短信服务实例

type TaskPollingAdaptor

type TaskPollingAdaptor interface {
	Init(info *relaycommon.RelayInfo)
	FetchTask(baseURL string, key string, body map[string]any, proxy string) (*http.Response, error)
	ParseTaskResult(body []byte) (*relaycommon.TaskInfo, error)
	// AdjustBillingOnComplete 在任务到达终态(成功/失败)时由轮询循环调用。
	// 返回正数触发差额结算(补扣/退还),返回 0 保持预扣费金额不变。
	AdjustBillingOnComplete(task *model.Task, taskResult *relaycommon.TaskInfo) int
}

TaskPollingAdaptor 定义轮询所需的最小适配器接口,避免 service -> relay 的循环依赖

type TieredResultWrapper

type TieredResultWrapper = billingexpr.TieredResult

TieredResultWrapper wraps billingexpr.TieredResult for use at the service layer.

type TokenDetails

type TokenDetails struct {
	TextTokens  int
	AudioTokens int
}

type ToolCallItem

type ToolCallItem struct {
	Name       string  `json:"name"`
	CallCount  int     `json:"call_count"`
	PricePer1K float64 `json:"price_per_1k"`
	TotalPrice float64 `json:"total_price"`
	Quota      int     `json:"quota"`
}

ToolCallItem represents a single billed tool usage line.

type ToolCallResult

type ToolCallResult struct {
	TotalQuota int            `json:"total_quota"`
	Items      []ToolCallItem `json:"items,omitempty"`
}

ToolCallResult holds the aggregated tool call billing for a request.

func ComputeToolCallQuota

func ComputeToolCallQuota(usage ToolCallUsage, groupRatio float64) ToolCallResult

ComputeToolCallQuota calculates the total quota for all tool calls in a request. Tool prices are resolved via GetToolPriceForModel which supports model-prefix overrides. groupRatio is applied.

type ToolCallUsage

type ToolCallUsage struct {
	ModelName              string
	WebSearchCalls         int
	WebSearchToolName      string // "web_search_preview", "web_search", etc.
	FileSearchCalls        int
	ImageGenerationCall    bool
	ImageGenerationQuality string
	ImageGenerationSize    string
}

ToolCallUsage captures all tool call counts from a single request.

type VendorSharePoint

type VendorSharePoint struct {
	Ts     string  `json:"ts"`
	Label  string  `json:"label"`
	Vendor string  `json:"vendor"`
	Share  float64 `json:"share"`
	Tokens int64   `json:"tokens"`
}

type VendorShareSeries

type VendorShareSeries struct {
	Points  []VendorSharePoint  `json:"points"`
	Vendors []VendorShareVendor `json:"vendors"`
	Buckets int                 `json:"buckets"`
}

type VendorShareVendor

type VendorShareVendor struct {
	Name  string  `json:"name"`
	Total int64   `json:"total"`
	Share float64 `json:"share"`
}

type WalletFunding

type WalletFunding struct {
	// contains filtered or unexported fields
}

func (*WalletFunding) PreConsume

func (w *WalletFunding) PreConsume(amount int) error

func (*WalletFunding) Refund

func (w *WalletFunding) Refund() error

func (*WalletFunding) Reserve

func (w *WalletFunding) Reserve(delta int) error

func (*WalletFunding) RollbackReserve

func (w *WalletFunding) RollbackReserve(delta int)

func (*WalletFunding) Settle

func (w *WalletFunding) Settle(delta int) error

func (*WalletFunding) Source

func (w *WalletFunding) Source() string

type WebhookPayload

type WebhookPayload struct {
	Type      string        `json:"type"`
	Title     string        `json:"title"`
	Content   string        `json:"content"`
	Values    []interface{} `json:"values,omitempty"`
	Timestamp int64         `json:"timestamp"`
}

WebhookPayload webhook 通知的负载数据

type WorkerRequest

type WorkerRequest struct {
	URL     string            `json:"url"`
	Key     string            `json:"key"`
	Method  string            `json:"method,omitempty"`
	Headers map[string]string `json:"headers,omitempty"`
	Body    json.RawMessage   `json:"body,omitempty"`
}

WorkerRequest Worker请求的数据结构

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL