Files
new-api-custom/relaykit/relayconvert/convmeta/meta.go
T

337 lines
9.5 KiB
Go

// Package convmeta defines the conversion-context contract between format
// converters (future relaykit) and the hosting application. Converters read
// protocol state and per-request options exclusively through the Meta
// interface; the host's RelayInfo implements it.
package convmeta
import (
"strings"
"github.com/QuantumNous/new-api/relaykit/dto"
"github.com/QuantumNous/new-api/relaykit/types"
)
// Meta is the only view of the relay session that format converters may use.
// It is satisfied by *relaycommon.RelayInfo on the host side; other embedders
// (tests, external relaykit users) can use *Values.
// Implementations backed by pointer types must make every method safe on a nil
// receiver: a typed-nil pointer stored in Meta is still a non-nil interface,
// and relaykit deliberately does not use reflection to detect that case.
type Meta interface {
GetOriginModelName() string
GetUpstreamModelName() string
// HasChannelMeta reports whether upstream channel information is attached;
// converters use it to decide if GetUpstreamModelName is meaningful.
HasChannelMeta() bool
GetChannelID() int
GetChannelType() int
GetIsStream() bool
GetReasoningEffort() string
// SetReasoningEffort records the effort level a converter derived from a
// model-name suffix so downstream billing/logging can see it.
SetReasoningEffort(effort string)
// ReasoningState returns the suffix-derived reasoning intent attached at
// the host entry layer. Standalone callers that do not set it receive nil;
// converters then use only explicit request fields.
ReasoningState() *dto.ReasoningConversionState
GetEstimatePromptTokens() int
// EnsureClaudeConvertInfo lazily creates and returns the mutable
// OpenAI→Claude stream conversion state. For non-nil receivers, the same
// instance must be returned for the lifetime of one streaming session; a
// nil receiver may return a temporary initialized state.
EnsureClaudeConvertInfo() *ClaudeConvertInfo
// ResponsesToolState returns the Responses tool encoding recorded by the
// latest request conversion, or nil when none was recorded. Response
// converters read it to restore Responses-only tool call shapes.
ResponsesToolState() *ResponsesToolState
// SetResponsesToolState replaces that record; nil clears it. Request
// conversion calls it on every attempt so retries never inherit a record
// from another channel.
SetResponsesToolState(state *ResponsesToolState)
// GetSendResponseCount / IncrSendResponseCount expose the shared
// downstream-chunk counter (the host may also increment it).
GetSendResponseCount() int
IncrSendResponseCount()
// AppendRequestConversion records a hop in the request format chain.
AppendRequestConversion(format types.RelayFormat)
// ConvOptions returns the request-scoped conversion options snapshot.
// Must never return nil.
ConvOptions() *Options
}
// ClaudeConvertInfo carries mutable state for OpenAI chat → Claude Messages
// stream conversion. Moved here from relay/common (which keeps an alias).
type ClaudeConvertInfo struct {
LastMessagesType string
Index int
Usage *dto.Usage
FinishReason string
Done bool
ToolCallBaseIndex int
ToolCallMaxIndexOffset int
ToolCalls []*ClaudeStreamToolCall
ToolCallByIndex map[int]*ClaudeStreamToolCall
ToolCallByID map[string]*ClaudeStreamToolCall
}
// ClaudeStreamToolCall tracks one OpenAI tool_calls entry while it is encoded
// as a Claude tool_use content block. Chat tool indexes and Claude content
// block indexes are separate domains, so the mapping must remain explicit.
type ClaudeStreamToolCall struct {
BlockIndex int
ID string
Name string
PendingArguments string
Started bool
}
// ResponsesToolState records how Responses-only tool definitions were encoded
// for the upstream protocol, so the matching response can be restored.
type ResponsesToolState struct {
// CustomToolNames lists Responses custom (freeform) tools that were sent
// upstream as function tools taking one string "input" argument. Function
// calls with these names are custom tool calls.
CustomToolNames map[string]struct{}
// Namespaces maps an upstream function name to the Responses tool
// namespace it was flattened from; see NamespacedToolName.
Namespaces map[string]string
}
// CustomToolInputArgument is the single function argument that carries a
// Responses custom tool input through an upstream function call.
const CustomToolInputArgument = "input"
// DefaultResponsesToolNamespace is the namespace Codex declares its ordinary
// tools in. OpenAI returns calls to its tools by bare name without a
// namespace field, so its tools keep their names upstream.
const DefaultResponsesToolNamespace = "functions"
// NamespacedToolName is the upstream function name of a tool declared in a
// Responses tool namespace: "<namespace>__<name>", or the bare name for the
// default namespace.
func NamespacedToolName(namespace string, name string) string {
if namespace == "" || namespace == DefaultResponsesToolNamespace {
return name
}
return namespace + "__" + name
}
// IsCustomTool reports whether name was encoded from a Responses custom tool.
func (s *ResponsesToolState) IsCustomTool(name string) bool {
if s == nil {
return false
}
_, ok := s.CustomToolNames[name]
return ok
}
// ResponsesToolName restores the Responses namespace and tool name of an
// upstream function name. Names that were not flattened from a namespace
// return an empty namespace and the name unchanged.
func (s *ResponsesToolState) ResponsesToolName(upstreamName string) (string, string) {
if s == nil {
return "", upstreamName
}
namespace, ok := s.Namespaces[upstreamName]
if !ok {
return "", upstreamName
}
return namespace, strings.TrimPrefix(upstreamName, namespace+"__")
}
const (
LastMessageTypeNone = "none"
LastMessageTypeText = "text"
LastMessageTypeTools = "tools"
LastMessageTypeThinking = "thinking"
)
// Values is a plain-struct Meta implementation for tests and non-RelayInfo
// hosts (the relaykit-native entry point).
type Values struct {
OriginModelName string
UpstreamModelName string
ChannelMetaAttached bool
ChannelID int
ChannelType int
IsStream bool
ReasoningEffort string
ReasoningConversion *dto.ReasoningConversionState
EstimatePromptTokens int
ClaudeConvertInfo *ClaudeConvertInfo
ResponsesTools *ResponsesToolState
SendResponseCount int
ConversionChain []types.RelayFormat
Options *Options
}
var _ Meta = (*Values)(nil)
func (v *Values) GetOriginModelName() string {
if v == nil {
return ""
}
return v.OriginModelName
}
func (v *Values) GetUpstreamModelName() string {
if v == nil {
return ""
}
return v.UpstreamModelName
}
func (v *Values) HasChannelMeta() bool {
return v != nil && v.ChannelMetaAttached
}
func (v *Values) GetChannelID() int {
if v == nil {
return 0
}
return v.ChannelID
}
func (v *Values) GetChannelType() int {
if v == nil {
return 0
}
return v.ChannelType
}
func (v *Values) GetIsStream() bool {
return v != nil && v.IsStream
}
func (v *Values) GetReasoningEffort() string {
if v == nil {
return ""
}
return v.ReasoningEffort
}
func (v *Values) SetReasoningEffort(effort string) {
if v != nil {
v.ReasoningEffort = effort
}
}
func (v *Values) ReasoningState() *dto.ReasoningConversionState {
if v == nil {
return nil
}
return v.ReasoningConversion
}
func (v *Values) GetEstimatePromptTokens() int {
if v == nil {
return 0
}
return v.EstimatePromptTokens
}
func (v *Values) EnsureClaudeConvertInfo() *ClaudeConvertInfo {
if v == nil {
return &ClaudeConvertInfo{LastMessagesType: LastMessageTypeNone}
}
if v.ClaudeConvertInfo == nil {
v.ClaudeConvertInfo = &ClaudeConvertInfo{LastMessagesType: LastMessageTypeNone}
}
return v.ClaudeConvertInfo
}
func (v *Values) ResponsesToolState() *ResponsesToolState {
if v == nil {
return nil
}
return v.ResponsesTools
}
func (v *Values) SetResponsesToolState(state *ResponsesToolState) {
if v != nil {
v.ResponsesTools = state
}
}
func (v *Values) GetSendResponseCount() int {
if v == nil {
return 0
}
return v.SendResponseCount
}
func (v *Values) IncrSendResponseCount() {
if v != nil {
v.SendResponseCount++
}
}
func (v *Values) AppendRequestConversion(format types.RelayFormat) {
if v == nil || format == "" {
return
}
if n := len(v.ConversionChain); n > 0 && v.ConversionChain[n-1] == format {
return
}
v.ConversionChain = append(v.ConversionChain, format)
}
func (v *Values) ConvOptions() *Options {
if v == nil {
return &Options{}
}
if v.Options == nil {
v.Options = &Options{}
}
return v.Options
}
// UpstreamModelName / ChannelTypeOf are nil-safe accessors for optional Meta
// values (converters are often called with a nil Meta in tests and compat
// shims).
func UpstreamModelName(m Meta) string {
if m == nil || !m.HasChannelMeta() {
return ""
}
return m.GetUpstreamModelName()
}
func ChannelTypeOf(m Meta) int {
if m == nil || !m.HasChannelMeta() {
return 0
}
return m.GetChannelType()
}
// OptionsOf returns m's conversion options, or empty defaults when m is nil.
func OptionsOf(m Meta) *Options {
if m == nil {
return &Options{}
}
return m.ConvOptions()
}
// ResponsesToolStateOf is a nil-safe reader for Meta.ResponsesToolState.
func ResponsesToolStateOf(m Meta) *ResponsesToolState {
if m == nil {
return nil
}
return m.ResponsesToolState()
}
// ReasoningStateOf is a nil-safe reader for Meta.ReasoningState.
func ReasoningStateOf(m Meta) *dto.ReasoningConversionState {
if m == nil {
return nil
}
return m.ReasoningState()
}