feat: publish sanitized custom.97 source

This commit is contained in:
cneicy committed 2026-10-27 20:43:39 +08:00
1 parent 8e6ed0317b
commit 86c0276156
1022 files changed
+69903 -19207

No files matched your search

+87
View File
@@ -5,6 +5,8 @@
package convmeta
import (
"strings"
"github.com/QuantumNous/new-api/relaykit/dto"
"github.com/QuantumNous/new-api/relaykit/types"
)
@@ -40,6 +42,15 @@ type Meta interface {
// 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
@@ -80,6 +91,60 @@ type ClaudeStreamToolCall struct {
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"
@@ -101,6 +166,7 @@ type Values struct {
EstimatePromptTokens int
ClaudeConvertInfo *ClaudeConvertInfo
ResponsesTools *ResponsesToolState
SendResponseCount int
ConversionChain []types.RelayFormat
@@ -182,6 +248,19 @@ func (v *Values) EnsureClaudeConvertInfo() *ClaudeConvertInfo {
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
@@ -240,6 +319,14 @@ func OptionsOf(m Meta) *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 {
+29 -6
View File
@@ -23,6 +23,12 @@ type Options struct {
// it from the channel type.
OpenRouterDialect bool
// WebSearch encodes hosted web search for the upstream dialect. Nil, or a
// nil result, keeps the default: web_search_options on Chat and the native
// hosted tool on Responses, Claude, and Gemini. The host sets it from the
// channel adaptor's Init.
WebSearch WebSearchEncoder
// PreserveThinkingSuffix reports models whose -thinking/-nothinking/effort
// suffix must be kept on the outgoing model name (host blacklist lookup).
// Nil means "never preserve".
@@ -34,9 +40,13 @@ type Options struct {
}
type ClaudeOptions struct {
// ThinkingAdapterEnabled controls whether suffix-derived reasoning intent
// is rendered onto Claude thinking / output_config. Suffix parsing itself
// is the host entry layer's job (standalone users call Parse* themselves).
// ThinkingAdapterEnabled is the host thinking-suffix adapter toggle. The
// host passes it to reasoning.ParseClaudeModelSuffix, so -thinking,
// -nothinking, -thinking-<budget>, and effort tails such as -high on
// claude-* names become reasoning intent only while it is on; when off the
// model name is sent verbatim. Explicit @ modifiers are unaffected. Suffix
// parsing itself is the host entry layer's job (standalone users call
// Parse* themselves).
ThinkingAdapterEnabled bool
// ThinkingAdapterBudgetTokensPercentage sizes thinking budget_tokens as a
// fraction of max_tokens when the adapter fires.
@@ -57,9 +67,12 @@ type ClaudeOptions struct {
}
type GeminiOptions struct {
// ThinkingAdapterEnabled controls whether suffix-derived reasoning intent
// is rendered onto Gemini thinkingConfig. Suffix parsing itself is the
// host entry layer's job (standalone users call Parse* themselves).
// ThinkingAdapterEnabled is the host thinking-suffix adapter toggle. The
// host and gemini_chat request conversion pass it to
// reasoning.ParseGeminiModelSuffix, so -thinking, -nothinking,
// -thinking-<budget>, and effort tails such as -high on gemini-* names
// become reasoning intent only while it is on; when off the model name is
// sent verbatim. Explicit @ modifiers are unaffected.
ThinkingAdapterEnabled bool
// ThinkingAdapterBudgetTokensPercentage sizes thinkingBudget as a fraction
// of maxOutputTokens when the adapter fires.
@@ -70,6 +83,12 @@ type GeminiOptions struct {
// SupportsImagine reports whether the model supports image generation
// (switches response modalities). Nil means "never".
SupportsImagine func(modelName string) bool
// SupportsJSONSchema reports whether the upstream accepts JSON Schema in
// parametersJsonSchema and responseJsonSchema for the model. A schema that
// Gemini's typed Schema cannot hold without loss is then sent there
// unchanged; otherwise it is cleaned to the typed Schema and the loss is
// reported as a diagnostic. Nil means "never".
SupportsJSONSchema func(modelName string) bool
// SafetySetting returns the harm threshold for a category. Nil or empty
// return means no safetySettings are attached.
SafetySetting func(category string) string
@@ -86,6 +105,10 @@ func (o *GeminiOptions) SupportsImagineModel(modelName string) bool {
return o != nil && o.SupportsImagine != nil && o.SupportsImagine(modelName)
}
func (o *GeminiOptions) SupportsJSONSchemaModel(modelName string) bool {
return o != nil && o.SupportsJSONSchema != nil && o.SupportsJSONSchema(modelName)
}
func (o *GeminiOptions) SafetySettingFor(category string) string {
if o == nil || o.SafetySetting == nil {
return ""
@@ -0,0 +1,95 @@
package convmeta
import (
"encoding/json"
"fmt"
"github.com/QuantumNous/new-api/relaykit/dto"
"github.com/QuantumNous/new-api/relaykit/types"
)
// WebSearch is the protocol-neutral hosted web-search specification decoded
// from the source request.
type WebSearch struct {
Location *ApproximateLocation
AllowedDomains []string
BlockedDomains []string
SearchContextSize string
MaxUses *int
AllowedCallers []string
ResponseInclusion string
ExternalWebAccess *bool
ReturnTokenBudget json.RawMessage
}
type ApproximateLocation struct {
City string
Region string
Country string
Timezone string
}
// WebSearchCall is one hosted web-search definition about to be encoded for
// the target request. Exactly one of Chat, Responses, Claude, Gemini is set;
// the encoder may write top-level fields of that request directly.
type WebSearchCall struct {
Source types.RelayFormat
Target types.RelayFormat
// NativeType is the source tool type, for example web_search_preview,
// web_search_20250305, or googleSearch.
NativeType string
// Index is the position in the source tool list. Chat Completions
// carries one hosted web search, so a Chat encoder sees only the first
// definition; the other targets call the encoder once per definition.
Index int
// Forced reports that the source tool_choice named the web-search tool.
Forced bool
Spec WebSearch
Chat *dto.GeneralOpenAIRequest
Responses *dto.OpenAIResponsesRequest
Claude *dto.ClaudeRequest
Gemini *dto.GeminiChatRequest
}
// WebSearchEncoding is what an encoder produced for one WebSearchCall.
type WebSearchEncoding struct {
// Tool is appended to the target tool list in the target protocol's own
// shape. A Chat target requires a dto.ToolCallRequest (any other type
// fails the conversion); the others take a JSON-marshalable value (usually
// map[string]any). Nil means the encoder wrote top-level request fields
// instead.
Tool any
// Omitted reports that no hosted search reaches the upstream. relaykit
// records hosted_web_search_omitted (error severity) so ToolLossPolicy
// can reject it, and appends no tool.
Omitted bool
// Diagnostics are merged into the conversion result. Error-severity
// diagnostics count as semantic loss: under the channel's "safe" or
// "strict" ToolLossPolicy they reject the request, so return
// ConversionDiagnosticError only when the search behavior the client
// asked for is actually lost, and ConversionDiagnosticWarning for tuning
// that does not change whether or what is searched.
Diagnostics []types.ConversionDiagnostic
}
// WebSearchEncoder encodes hosted web search for an upstream whose dialect
// differs from the target protocol's default. A nil result keeps relaykit's
// default encoding; an error fails the conversion and is reserved for
// encodings that cannot be produced at all, never for ordinary loss.
type WebSearchEncoder func(call WebSearchCall) (*WebSearchEncoding, error)
// Path is the diagnostic path of the definition in the source tool list.
func (c WebSearchCall) Path() string {
return fmt.Sprintf("tools[%d]", c.Index)
}
// SemanticLoss builds an error-severity diagnostic for this definition.
func (c WebSearchCall) SemanticLoss(code, message string) types.ConversionDiagnostic {
return types.ConversionDiagnostic{Code: code, Path: c.Path(), Message: message, Severity: types.ConversionDiagnosticError}
}
// PresentationLoss builds a warning-severity diagnostic for this definition.
func (c WebSearchCall) PresentationLoss(code, message string) types.ConversionDiagnostic {
return types.ConversionDiagnostic{Code: code, Path: c.Path(), Message: message, Severity: types.ConversionDiagnosticWarning}
}