feat: publish v1.0.0-rc.36-custom.1
Publish Docker image (Multi-arch) / Build & push (amd64) (push) Failing after 9m20s
Release (Linux, macOS, Windows) / Linux Release (push) Failing after 55m7s
Publish Docker image (Multi-arch) / Build & push (arm64) (push) Canceled after 0s
Release (Linux, macOS, Windows) / macOS Release (push) Canceled after 0s
Release (Linux, macOS, Windows) / Windows Release (push) Canceled after 0s
Publish Docker image (Multi-arch) / Create multi-arch manifests (push) Canceled after 0s

This commit is contained in:
Codex committed 2026-09-11 05:07:48 +08:00
commit bb7968b612
2381 files changed
+551494

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
package types
type ChannelError struct {
ChannelId int `json:"channel_id"`
ChannelType int `json:"channel_type"`
ChannelName string `json:"channel_name"`
IsMultiKey bool `json:"is_multi_key"`
AutoBan bool `json:"auto_ban"`
UsingKey string `json:"using_key"`
}
func NewChannelError(channelId int, channelType int, channelName string, isMultiKey bool, usingKey string, autoBan bool) *ChannelError {
return &ChannelError{
ChannelId: channelId,
ChannelType: channelType,
ChannelName: channelName,
IsMultiKey: isMultiKey,
AutoBan: autoBan,
UsingKey: usingKey,
}
}
+75
View File
@@ -0,0 +1,75 @@
package types
import (
"fmt"
"strings"
)
type ConversionDiagnosticSeverity string
const (
ConversionDiagnosticWarning ConversionDiagnosticSeverity = "warning"
ConversionDiagnosticError ConversionDiagnosticSeverity = "error"
)
type ConversionDiagnostic struct {
Code string `json:"code"`
Path string `json:"path,omitempty"`
Message string `json:"message"`
Severity ConversionDiagnosticSeverity `json:"severity"`
From RelayFormat `json:"from"`
To RelayFormat `json:"to"`
}
type ConversionLossPolicy string
const (
// ConversionLossPolicySafe rejects request-phase conversions that would
// change tool execution semantics, while returning non-fatal loss as
// diagnostics. It is opt-in; the default is ConversionLossPolicyAllow.
ConversionLossPolicySafe ConversionLossPolicy = "safe"
// ConversionLossPolicyStrict rejects every lossy conversion, including
// presentation-only metadata loss.
ConversionLossPolicyStrict ConversionLossPolicy = "strict"
// ConversionLossPolicyAllow is the default. It permits lossy conversion
// and reports every loss through the conversion result.
ConversionLossPolicyAllow ConversionLossPolicy = "allow"
)
type ConversionLossError struct {
Diagnostics []ConversionDiagnostic
}
func (e *ConversionLossError) Error() string {
if e == nil || len(e.Diagnostics) == 0 {
return "conversion would lose protocol semantics"
}
messages := make([]string, 0, len(e.Diagnostics))
for _, diagnostic := range e.Diagnostics {
message := diagnostic.Message
if message == "" {
message = diagnostic.Code
}
if diagnostic.Path != "" {
message = fmt.Sprintf("%s: %s", diagnostic.Path, message)
}
messages = append(messages, message)
}
return "conversion would lose protocol semantics: " + strings.Join(messages, "; ")
}
func RejectConversionLoss(policy ConversionLossPolicy, diagnostics []ConversionDiagnostic) error {
if policy == ConversionLossPolicyAllow || len(diagnostics) == 0 {
return nil
}
rejected := make([]ConversionDiagnostic, 0, len(diagnostics))
for _, diagnostic := range diagnostics {
if policy == ConversionLossPolicyStrict || diagnostic.Severity == ConversionDiagnosticError {
rejected = append(rejected, diagnostic)
}
}
if len(rejected) == 0 {
return nil
}
return &ConversionLossError{Diagnostics: rejected}
}
+30
View File
@@ -0,0 +1,30 @@
package types
// EndpointType identifies a downstream API surface. Moved from constant so
// the conversion kit (dto/relayconvert) has no host imports; constant keeps
// aliases for host code.
type EndpointType string
const (
EndpointTypeOpenAI EndpointType = "openai"
EndpointTypeOpenAIResponse EndpointType = "openai-response"
EndpointTypeOpenAIResponseCompact EndpointType = "openai-response-compact"
EndpointTypeOpenAIAlphaSearch EndpointType = "openai-alpha-search"
EndpointTypeAnthropic EndpointType = "anthropic"
EndpointTypeGemini EndpointType = "gemini"
EndpointTypeJinaRerank EndpointType = "jina-rerank"
EndpointTypeImageGeneration EndpointType = "image-generation"
EndpointTypeEmbeddings EndpointType = "embeddings"
EndpointTypeOpenAIVideo EndpointType = "openai-video"
)
// Finish reasons shared by the OpenAI-compatible response formats.
// Declared as vars (not consts) because converter code takes their address
// for *string finish-reason fields.
var (
FinishReasonStop = "stop"
FinishReasonToolCalls = "tool_calls"
FinishReasonLength = "length"
FinishReasonFunctionCall = "function_call"
FinishReasonContentFilter = "content_filter"
)
+417
View File
@@ -0,0 +1,417 @@
package types
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
kitutil "github.com/QuantumNous/new-api/relaykit/relayconvert/kitutil"
)
type OpenAIError struct {
Message string `json:"message"`
Type string `json:"type"`
Param string `json:"param"`
Code any `json:"code"`
Metadata json.RawMessage `json:"metadata,omitempty"`
}
type ClaudeError struct {
Type string `json:"type,omitempty"`
Message string `json:"message,omitempty"`
}
type ErrorType string
const (
ErrorTypeNewAPIError ErrorType = "new_api_error"
ErrorTypeOpenAIError ErrorType = "openai_error"
ErrorTypeClaudeError ErrorType = "claude_error"
ErrorTypeMidjourneyError ErrorType = "midjourney_error"
ErrorTypeGeminiError ErrorType = "gemini_error"
ErrorTypeRerankError ErrorType = "rerank_error"
ErrorTypeUpstreamError ErrorType = "upstream_error"
)
type ErrorCode string
const (
ErrorCodeInvalidRequest ErrorCode = "invalid_request"
ErrorCodeSensitiveWordsDetected ErrorCode = "sensitive_words_detected"
ErrorCodeViolationFeeGrokCSAM ErrorCode = "violation_fee.grok.csam"
// new api error
ErrorCodeCountTokenFailed ErrorCode = "count_token_failed"
ErrorCodeModelPriceError ErrorCode = "model_price_error"
ErrorCodeInvalidApiType ErrorCode = "invalid_api_type"
ErrorCodeJsonMarshalFailed ErrorCode = "json_marshal_failed"
ErrorCodeDoRequestFailed ErrorCode = "do_request_failed"
ErrorCodeGetChannelFailed ErrorCode = "get_channel_failed"
ErrorCodeGenRelayInfoFailed ErrorCode = "gen_relay_info_failed"
// channel error
ErrorCodeChannelNoAvailableKey ErrorCode = "channel:no_available_key"
ErrorCodeChannelParamOverrideInvalid ErrorCode = "channel:param_override_invalid"
ErrorCodeChannelHeaderOverrideInvalid ErrorCode = "channel:header_override_invalid"
ErrorCodeChannelModelMappedError ErrorCode = "channel:model_mapped_error"
ErrorCodeChannelAwsClientError ErrorCode = "channel:aws_client_error"
ErrorCodeChannelInvalidKey ErrorCode = "channel:invalid_key"
ErrorCodeChannelResponseTimeExceeded ErrorCode = "channel:response_time_exceeded"
// client request error
ErrorCodeReadRequestBodyFailed ErrorCode = "read_request_body_failed"
ErrorCodeConvertRequestFailed ErrorCode = "convert_request_failed"
ErrorCodeAccessDenied ErrorCode = "access_denied"
// request error
ErrorCodeBadRequestBody ErrorCode = "bad_request_body"
// response error
ErrorCodeReadResponseBodyFailed ErrorCode = "read_response_body_failed"
ErrorCodeBadResponseStatusCode ErrorCode = "bad_response_status_code"
ErrorCodeBadResponse ErrorCode = "bad_response"
ErrorCodeBadResponseBody ErrorCode = "bad_response_body"
ErrorCodeEmptyResponse ErrorCode = "empty_response"
ErrorCodeAwsInvokeError ErrorCode = "aws_invoke_error"
ErrorCodeModelNotFound ErrorCode = "model_not_found"
ErrorCodePromptBlocked ErrorCode = "prompt_blocked"
// sql error
ErrorCodeQueryDataError ErrorCode = "query_data_error"
ErrorCodeUpdateDataError ErrorCode = "update_data_error"
// quota error
ErrorCodeInsufficientUserQuota ErrorCode = "insufficient_user_quota"
ErrorCodePreConsumeTokenQuotaFailed ErrorCode = "pre_consume_token_quota_failed"
)
type NewAPIError struct {
Err error
RelayError any
skipRetry bool
recordErrorLog *bool
errorType ErrorType
errorCode ErrorCode
StatusCode int
Metadata json.RawMessage
}
// Unwrap enables errors.Is / errors.As to work with NewAPIError by exposing the underlying error.
func (e *NewAPIError) Unwrap() error {
if e == nil {
return nil
}
return e.Err
}
func (e *NewAPIError) GetErrorCode() ErrorCode {
if e == nil {
return ""
}
return e.errorCode
}
func (e *NewAPIError) GetErrorType() ErrorType {
if e == nil {
return ""
}
return e.errorType
}
func (e *NewAPIError) Error() string {
if e == nil {
return ""
}
if e.Err == nil {
// fallback message when underlying error is missing
return string(e.errorCode)
}
return e.Err.Error()
}
func (e *NewAPIError) ErrorWithStatusCode() string {
if e == nil {
return ""
}
msg := e.Error()
if e.StatusCode == 0 {
return msg
}
if msg == "" {
return fmt.Sprintf("status_code=%d", e.StatusCode)
}
return fmt.Sprintf("status_code=%d, %s", e.StatusCode, msg)
}
func (e *NewAPIError) MaskSensitiveError() string {
if e == nil {
return ""
}
if e.Err == nil {
return string(e.errorCode)
}
errStr := e.Err.Error()
if e.errorCode == ErrorCodeCountTokenFailed {
return errStr
}
return kitutil.MaskSensitiveInfo(errStr)
}
func (e *NewAPIError) MaskSensitiveErrorWithStatusCode() string {
if e == nil {
return ""
}
msg := e.MaskSensitiveError()
if e.StatusCode == 0 {
return msg
}
if msg == "" {
return fmt.Sprintf("status_code=%d", e.StatusCode)
}
return fmt.Sprintf("status_code=%d, %s", e.StatusCode, msg)
}
func (e *NewAPIError) SetMessage(message string) {
e.Err = errors.New(message)
}
func (e *NewAPIError) ToOpenAIError() OpenAIError {
var result OpenAIError
switch e.errorType {
case ErrorTypeOpenAIError:
if openAIError, ok := e.RelayError.(OpenAIError); ok {
result = openAIError
}
case ErrorTypeClaudeError:
if claudeError, ok := e.RelayError.(ClaudeError); ok {
result = OpenAIError{
Message: e.Error(),
Type: claudeError.Type,
Param: "",
Code: e.errorCode,
}
}
default:
result = OpenAIError{
Message: e.Error(),
Type: string(e.errorType),
Param: "",
Code: e.errorCode,
}
}
if e.errorCode != ErrorCodeCountTokenFailed {
result.Message = kitutil.MaskSensitiveInfo(result.Message)
}
if result.Message == "" {
result.Message = string(e.errorType)
}
return result
}
func (e *NewAPIError) ToClaudeError() ClaudeError {
var result ClaudeError
switch e.errorType {
case ErrorTypeOpenAIError:
if openAIError, ok := e.RelayError.(OpenAIError); ok {
result = ClaudeError{
Message: e.Error(),
Type: fmt.Sprintf("%v", openAIError.Code),
}
}
case ErrorTypeClaudeError:
if claudeError, ok := e.RelayError.(ClaudeError); ok {
result = claudeError
}
default:
result = ClaudeError{
Message: e.Error(),
Type: string(e.errorType),
}
}
if e.errorCode != ErrorCodeCountTokenFailed {
result.Message = kitutil.MaskSensitiveInfo(result.Message)
}
if result.Message == "" {
result.Message = string(e.errorType)
}
return result
}
type NewAPIErrorOptions func(*NewAPIError)
func NewError(err error, errorCode ErrorCode, ops ...NewAPIErrorOptions) *NewAPIError {
var newErr *NewAPIError
// 保留深层传递的 new err
if errors.As(err, &newErr) {
for _, op := range ops {
op(newErr)
}
return newErr
}
e := &NewAPIError{
Err: err,
RelayError: nil,
errorType: ErrorTypeNewAPIError,
StatusCode: http.StatusInternalServerError,
errorCode: errorCode,
}
for _, op := range ops {
op(e)
}
return e
}
func NewOpenAIError(err error, errorCode ErrorCode, statusCode int, ops ...NewAPIErrorOptions) *NewAPIError {
var newErr *NewAPIError
// 保留深层传递的 new err
if errors.As(err, &newErr) {
if newErr.RelayError == nil {
openaiError := OpenAIError{
Message: newErr.Error(),
Type: string(errorCode),
Code: errorCode,
}
newErr.RelayError = openaiError
}
for _, op := range ops {
op(newErr)
}
return newErr
}
openaiError := OpenAIError{
Message: err.Error(),
Type: string(errorCode),
Code: errorCode,
}
return WithOpenAIError(openaiError, statusCode, ops...)
}
func InitOpenAIError(errorCode ErrorCode, statusCode int, ops ...NewAPIErrorOptions) *NewAPIError {
openaiError := OpenAIError{
Type: string(errorCode),
Code: errorCode,
}
return WithOpenAIError(openaiError, statusCode, ops...)
}
func NewErrorWithStatusCode(err error, errorCode ErrorCode, statusCode int, ops ...NewAPIErrorOptions) *NewAPIError {
e := &NewAPIError{
Err: err,
RelayError: OpenAIError{
Message: err.Error(),
Type: string(errorCode),
},
errorType: ErrorTypeNewAPIError,
StatusCode: statusCode,
errorCode: errorCode,
}
for _, op := range ops {
op(e)
}
return e
}
func WithOpenAIError(openAIError OpenAIError, statusCode int, ops ...NewAPIErrorOptions) *NewAPIError {
code, ok := openAIError.Code.(string)
if !ok {
if openAIError.Code != nil {
code = fmt.Sprintf("%v", openAIError.Code)
} else {
code = "unknown_error"
}
}
if openAIError.Type == "" {
openAIError.Type = "upstream_error"
}
e := &NewAPIError{
RelayError: openAIError,
errorType: ErrorTypeOpenAIError,
StatusCode: statusCode,
Err: errors.New(openAIError.Message),
errorCode: ErrorCode(code),
}
// OpenRouter
if len(openAIError.Metadata) > 0 {
openAIError.Message = fmt.Sprintf("%s (%s)", openAIError.Message, openAIError.Metadata)
e.Metadata = openAIError.Metadata
e.RelayError = openAIError
e.Err = errors.New(openAIError.Message)
}
for _, op := range ops {
op(e)
}
return e
}
func WithClaudeError(claudeError ClaudeError, statusCode int, ops ...NewAPIErrorOptions) *NewAPIError {
if claudeError.Type == "" {
claudeError.Type = "upstream_error"
}
e := &NewAPIError{
RelayError: claudeError,
errorType: ErrorTypeClaudeError,
StatusCode: statusCode,
Err: errors.New(claudeError.Message),
errorCode: ErrorCode(claudeError.Type),
}
for _, op := range ops {
op(e)
}
return e
}
func IsChannelError(err *NewAPIError) bool {
if err == nil {
return false
}
return strings.HasPrefix(string(err.errorCode), "channel:")
}
func IsSkipRetryError(err *NewAPIError) bool {
if err == nil {
return false
}
return err.skipRetry
}
func ErrOptionWithSkipRetry() NewAPIErrorOptions {
return func(e *NewAPIError) {
e.skipRetry = true
}
}
func ErrOptionWithNoRecordErrorLog() NewAPIErrorOptions {
return func(e *NewAPIError) {
e.recordErrorLog = kitutil.GetPointer(false)
}
}
func ErrOptionWithStatusCode(statusCode int) NewAPIErrorOptions {
return func(e *NewAPIError) {
e.StatusCode = statusCode
}
}
func ErrOptionWithHideErrMsg(replaceStr string) NewAPIErrorOptions {
return func(e *NewAPIError) {
if kitutil.Debug.Load() {
fmt.Printf("ErrOptionWithHideErrMsg: %s, origin error: %s", replaceStr, e.Err)
}
e.Err = errors.New(replaceStr)
}
}
func IsRecordErrorLog(e *NewAPIError) bool {
if e == nil {
return false
}
if e.recordErrorLog == nil {
// default to true if not set
return true
}
return *e.recordErrorLog
}
+8
View File
@@ -0,0 +1,8 @@
package types
type LocalFileData struct {
MimeType string
Base64Data string
Url string
Size int64
}
+232
View File
@@ -0,0 +1,232 @@
package types
import (
"fmt"
"image"
"os"
"strings"
"sync"
)
// FileSource 统一的文件来源抽象接口
// 支持 URL 和 base64 两种来源,提供懒加载和缓存机制
type FileSource interface {
IsURL() bool
GetIdentifier() string
GetRawData() string
ClearRawData()
SetCache(data *CachedFileData)
GetCache() *CachedFileData
HasCache() bool
ClearCache()
IsRegistered() bool
SetRegistered(registered bool)
Mu() *sync.Mutex
}
// baseFileSource 共享的缓存/锁/清理注册状态
type baseFileSource struct {
cachedData *CachedFileData
cacheLoaded bool
registered bool
mu sync.Mutex
}
func (b *baseFileSource) SetCache(data *CachedFileData) {
b.cachedData = data
b.cacheLoaded = true
}
func (b *baseFileSource) GetCache() *CachedFileData {
return b.cachedData
}
func (b *baseFileSource) HasCache() bool {
return b.cacheLoaded && b.cachedData != nil
}
func (b *baseFileSource) ClearCache() {
if b.cachedData != nil {
b.cachedData.Close()
}
b.cachedData = nil
b.cacheLoaded = false
}
func (b *baseFileSource) IsRegistered() bool {
return b.registered
}
func (b *baseFileSource) SetRegistered(registered bool) {
b.registered = registered
}
func (b *baseFileSource) Mu() *sync.Mutex {
return &b.mu
}
// ---------------------------------------------------------------------------
// URLSource — URL 来源的 FileSource 实现
// ---------------------------------------------------------------------------
type URLSource struct {
baseFileSource
URL string
}
func (u *URLSource) IsURL() bool { return true }
func (u *URLSource) GetIdentifier() string {
if len(u.URL) > 100 {
return u.URL[:100] + "..."
}
return u.URL
}
func (u *URLSource) GetRawData() string { return u.URL }
func (u *URLSource) ClearRawData() {}
// ---------------------------------------------------------------------------
// Base64Source — Base64 内联数据来源的 FileSource 实现
// ---------------------------------------------------------------------------
type Base64Source struct {
baseFileSource
Base64Data string
MimeType string
}
func (b *Base64Source) IsURL() bool { return false }
func (b *Base64Source) GetIdentifier() string {
if len(b.Base64Data) > 50 {
return "base64:" + b.Base64Data[:50] + "..."
}
return "base64:" + b.Base64Data
}
func (b *Base64Source) GetRawData() string { return b.Base64Data }
func (b *Base64Source) ClearRawData() {
if len(b.Base64Data) > 1024 {
b.Base64Data = ""
}
}
// ---------------------------------------------------------------------------
// Constructors
// ---------------------------------------------------------------------------
func NewURLFileSource(url string) *URLSource {
return &URLSource{URL: url}
}
func NewBase64FileSource(base64Data string, mimeType string) *Base64Source {
return &Base64Source{
Base64Data: base64Data,
MimeType: mimeType,
}
}
func NewFileSourceFromData(data string, mimeType string) FileSource {
if strings.HasPrefix(data, "http://") || strings.HasPrefix(data, "https://") {
return NewURLFileSource(data)
}
return NewBase64FileSource(data, mimeType)
}
// ---------------------------------------------------------------------------
// CachedFileData — 缓存的文件数据(支持内存和磁盘两种模式)
// ---------------------------------------------------------------------------
type CachedFileData struct {
base64Data string // 内存中的 base64 数据(小文件)
MimeType string // MIME 类型
Size int64 // 文件大小(字节)
DiskSize int64 // 磁盘缓存实际占用大小(字节,通常是 base64 长度)
ImageConfig *image.Config // 图片配置(如果是图片)
ImageFormat string // 图片格式(如果是图片)
diskPath string // 磁盘缓存文件路径(大文件)
isDisk bool // 是否使用磁盘缓存
diskMu sync.Mutex // 磁盘操作锁(保护磁盘文件的读取和删除)
diskClosed bool // 是否已关闭/清理
statDecremented bool // 是否已扣减统计
OnClose func(size int64)
}
func NewMemoryCachedData(base64Data string, mimeType string, size int64) *CachedFileData {
return &CachedFileData{
base64Data: base64Data,
MimeType: mimeType,
Size: size,
isDisk: false,
}
}
func NewDiskCachedData(diskPath string, mimeType string, size int64) *CachedFileData {
return &CachedFileData{
diskPath: diskPath,
MimeType: mimeType,
Size: size,
isDisk: true,
}
}
func (c *CachedFileData) GetBase64Data() (string, error) {
if !c.isDisk {
return c.base64Data, nil
}
c.diskMu.Lock()
defer c.diskMu.Unlock()
if c.diskClosed {
return "", fmt.Errorf("disk cache already closed")
}
data, err := os.ReadFile(c.diskPath)
if err != nil {
return "", fmt.Errorf("failed to read from disk cache: %w", err)
}
return string(data), nil
}
func (c *CachedFileData) SetBase64Data(data string) {
if !c.isDisk {
c.base64Data = data
}
}
func (c *CachedFileData) IsDisk() bool {
return c.isDisk
}
func (c *CachedFileData) Close() error {
if !c.isDisk {
c.base64Data = ""
return nil
}
c.diskMu.Lock()
defer c.diskMu.Unlock()
if c.diskClosed {
return nil
}
c.diskClosed = true
if c.diskPath != "" {
err := os.Remove(c.diskPath)
if err == nil && !c.statDecremented && c.OnClose != nil {
c.OnClose(c.DiskSize)
c.statDecremented = true
}
return err
}
return nil
}
+20
View File
@@ -0,0 +1,20 @@
package types
type RelayFormat string
const (
RelayFormatOpenAI RelayFormat = "openai"
RelayFormatClaude = "claude"
RelayFormatGemini = "gemini"
RelayFormatOpenAIResponses = "openai_responses"
RelayFormatOpenAIResponsesCompaction = "openai_responses_compaction"
RelayFormatOpenAIAlphaSearch = "openai_alpha_search"
RelayFormatOpenAIAudio = "openai_audio"
RelayFormatOpenAIImage = "openai_image"
RelayFormatOpenAIRealtime = "openai_realtime"
RelayFormatRerank = "rerank"
RelayFormatEmbedding = "embedding"
RelayFormatTask = "task"
RelayFormatMjProxy = "mj_proxy"
)
+84
View File
@@ -0,0 +1,84 @@
package types
type FileType string
const (
FileTypeImage FileType = "image" // Image file type
FileTypeAudio FileType = "audio" // Audio file type
FileTypeVideo FileType = "video" // Video file type
FileTypeFile FileType = "file" // Generic file type
)
type TokenType string
const (
TokenTypeTextNumber TokenType = "text_number" // Text or number tokens
TokenTypeTokenizer TokenType = "tokenizer" // Tokenizer tokens
TokenTypeImage TokenType = "image" // Image tokens
)
type TokenCountMeta struct {
TokenType TokenType `json:"token_type,omitempty"` // Type of tokens used in the request
CombineText string `json:"combine_text,omitempty"` // Combined text from all messages
ToolsCount int `json:"tools_count,omitempty"` // Number of tools used
NameCount int `json:"name_count,omitempty"` // Number of names in the request
MessagesCount int `json:"messages_count,omitempty"` // Number of messages in the request
Files []*FileMeta `json:"files,omitempty"` // List of files, each with type and content
MaxTokens int `json:"max_tokens,omitempty"` // Maximum tokens allowed in the request
ImagePriceRatio float64 `json:"image_ratio,omitempty"` // Ratio for image size, if applicable
BillingRatios map[string]float64 `json:"billing_ratios,omitempty"` // Validated request multipliers used by pre-consume billing
//IsStreaming bool `json:"is_streaming,omitempty"` // Indicates if the request is streaming
}
type FileMeta struct {
FileType
Source FileSource // 统一的文件来源(URL 或 base64)
Detail string // 图片细节级别(low/high/auto)
}
// NewFileMeta 创建新的 FileMeta
func NewFileMeta(fileType FileType, source FileSource) *FileMeta {
return &FileMeta{
FileType: fileType,
Source: source,
}
}
// NewImageFileMeta 创建图片类型的 FileMeta
func NewImageFileMeta(source FileSource, detail string) *FileMeta {
return &FileMeta{
FileType: FileTypeImage,
Source: source,
Detail: detail,
}
}
// GetIdentifier 获取文件标识符(用于日志)
func (f *FileMeta) GetIdentifier() string {
if f.Source != nil {
return f.Source.GetIdentifier()
}
return "unknown"
}
// IsURL 判断是否是 URL 来源
func (f *FileMeta) IsURL() bool {
return f.Source != nil && f.Source.IsURL()
}
// GetRawData 获取原始数据(兼容旧代码)
// Deprecated: 请使用 Source.GetRawData()
func (f *FileMeta) GetRawData() string {
if f.Source != nil {
return f.Source.GetRawData()
}
return ""
}
type RequestMeta struct {
OriginalModelName string `json:"original_model_name"`
UserUsingGroup string `json:"user_using_group"`
PromptTokens int `json:"prompt_tokens"`
PreConsumedQuota int `json:"pre_consumed_quota"`
}