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

+135
View File
@@ -0,0 +1,135 @@
// Package kitutil holds the dependency-free helpers shared by the conversion
// kit packages (dto, types, relayconvert). It moved out of the host's common
// package as part of the relaykit extraction; common re-exports these for
// host code.
package kitutil
import (
"bytes"
"encoding/json"
"io"
"unsafe"
)
// Codec is the JSON engine used by every kitutil JSON helper. The host may
// replace it once at startup via SetCodec; relaykit itself never depends on a
// third-party JSON library.
type Codec interface {
Marshal(v any) ([]byte, error)
Unmarshal(data []byte, v any) error
Decode(r io.Reader, v any) error
Valid(data []byte) bool
}
// stdCodec is the default Codec, backed by encoding/json. It is the only place
// in relaykit that calls the standard library's JSON functions directly.
type stdCodec struct{}
func (stdCodec) Marshal(v any) ([]byte, error) {
return json.Marshal(v)
}
func (stdCodec) Unmarshal(data []byte, v any) error {
return json.Unmarshal(data, v)
}
func (stdCodec) Decode(r io.Reader, v any) error {
return json.NewDecoder(r).Decode(v)
}
func (stdCodec) Valid(data []byte) bool {
return json.Valid(data)
}
var codec Codec = stdCodec{}
// SetCodec installs the JSON engine behind every kitutil JSON helper, which
// also covers the custom (Un)MarshalJSON methods on relaykit DTOs. Like
// SetLogging it is meant to be called once during host startup before any
// request is served; it is not synchronized against concurrent helper calls.
// A nil codec is ignored so the standard-library default stays in place.
func SetCodec(c Codec) {
if c == nil {
return
}
codec = c
}
func Unmarshal(data []byte, v any) error {
return codec.Unmarshal(data, v)
}
func UnmarshalJsonStr(data string, v any) error {
return codec.Unmarshal(StringToByteSlice(data), v)
}
func DecodeJson(reader io.Reader, v any) error {
return codec.Decode(reader, v)
}
func Marshal(v any) ([]byte, error) {
return codec.Marshal(v)
}
// Valid reports whether data is a syntactically valid JSON document.
func Valid(data []byte) bool {
return codec.Valid(data)
}
func GetJsonType(data json.RawMessage) string {
trimmed := bytes.TrimSpace(data)
if len(trimmed) == 0 {
return "unknown"
}
firstChar := trimmed[0]
switch firstChar {
case '{':
return "object"
case '[':
return "array"
case '"':
return "string"
case 't', 'f':
return "boolean"
case 'n':
return "null"
default:
return "number"
}
}
// JsonRawMessageToString returns JSON strings as their decoded value and other JSON values as raw text.
func JsonRawMessageToString(data json.RawMessage) string {
trimmed := bytes.TrimSpace(data)
if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
return ""
}
if trimmed[0] != '"' {
return string(trimmed)
}
var value string
if err := Unmarshal(trimmed, &value); err != nil {
return string(trimmed)
}
return value
}
func StringToByteSlice(s string) []byte {
tmp1 := (*[2]uintptr)(unsafe.Pointer(&s))
tmp2 := [3]uintptr{tmp1[0], tmp1[1], tmp1[1]}
return *(*[]byte)(unsafe.Pointer(&tmp2))
}
func Any2Type[T any](data any) (T, error) {
var zero T
encoded, err := Marshal(data)
if err != nil {
return zero, err
}
var res T
err = Unmarshal(encoded, &res)
if err != nil {
return zero, err
}
return res, nil
}
@@ -0,0 +1,83 @@
package kitutil
import (
"encoding/json"
"io"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// recordingCodec notes which Codec method each helper reaches and delegates to
// the standard-library default so the helpers still produce real results.
type recordingCodec struct {
calls []string
}
func (c *recordingCodec) Marshal(v any) ([]byte, error) {
c.calls = append(c.calls, "Marshal")
return stdCodec{}.Marshal(v)
}
func (c *recordingCodec) Unmarshal(data []byte, v any) error {
c.calls = append(c.calls, "Unmarshal")
return stdCodec{}.Unmarshal(data, v)
}
func (c *recordingCodec) Decode(r io.Reader, v any) error {
c.calls = append(c.calls, "Decode")
return stdCodec{}.Decode(r, v)
}
func (c *recordingCodec) Valid(data []byte) bool {
c.calls = append(c.calls, "Valid")
return stdCodec{}.Valid(data)
}
func TestJSONHelpersRouteThroughInjectedCodec(t *testing.T) {
t.Cleanup(func() { SetCodec(stdCodec{}) })
fake := &recordingCodec{}
SetCodec(fake)
// A nil codec must not displace the installed one.
SetCodec(nil)
encoded, err := Marshal(map[string]int{"a": 1})
require.NoError(t, err)
assert.Equal(t, `{"a":1}`, string(encoded))
var fromBytes map[string]int
require.NoError(t, Unmarshal([]byte(`{"b":2}`), &fromBytes))
assert.Equal(t, map[string]int{"b": 2}, fromBytes)
var fromString map[string]int
require.NoError(t, UnmarshalJsonStr(`{"c":3}`, &fromString))
assert.Equal(t, map[string]int{"c": 3}, fromString)
var decoded map[string]int
require.NoError(t, DecodeJson(strings.NewReader(`{"d":4}`), &decoded))
assert.Equal(t, map[string]int{"d": 4}, decoded)
assert.True(t, Valid([]byte(`[]`)))
assert.False(t, Valid([]byte(`[`)))
converted, err := Any2Type[map[string]int](map[string]any{"e": 5})
require.NoError(t, err)
assert.Equal(t, map[string]int{"e": 5}, converted)
assert.Equal(t, "hello", JsonRawMessageToString(json.RawMessage(`"hello"`)))
assert.Equal(t, []string{
"Marshal", // Marshal
"Unmarshal", // Unmarshal
"Unmarshal", // UnmarshalJsonStr
"Decode", // DecodeJson
"Valid", // Valid (well-formed)
"Valid", // Valid (malformed)
"Marshal", // Any2Type encode
"Unmarshal", // Any2Type decode
"Unmarshal", // JsonRawMessageToString string literal
}, fake.calls)
}
+65
View File
@@ -0,0 +1,65 @@
package kitutil
import (
"fmt"
"os"
"sync/atomic"
)
// Kit packages log rare data-shape anomalies through these hooks. The host
// redirects them into its logging system at startup; standalone relaykit users
// get stderr defaults.
type LogFunc func(message string)
var (
logInfo atomic.Pointer[LogFunc]
logError atomic.Pointer[LogFunc]
logSystemError atomic.Pointer[LogFunc]
)
func SetLogging(info LogFunc, errorFn LogFunc) {
if info != nil {
logInfo.Store(&info)
}
if errorFn != nil {
logError.Store(&errorFn)
}
}
// SetSystemErrorLogging configures the hook for internal converter failures.
func SetSystemErrorLogging(errorFn LogFunc) {
if errorFn != nil {
logSystemError.Store(&errorFn)
}
}
func LogInfo(message string) {
if fn := logInfo.Load(); fn != nil {
(*fn)(message)
return
}
fmt.Fprintf(os.Stderr, "[relaykit] %s\n", message)
}
func LogError(message string) {
if fn := logError.Load(); fn != nil {
(*fn)(message)
return
}
fmt.Fprintf(os.Stderr, "[relaykit] ERROR %s\n", message)
}
// LogSystemError reports an internal converter failure through its dedicated
// hook, keeping it distinct from malformed request-data diagnostics.
func LogSystemError(message string) {
if fn := logSystemError.Load(); fn != nil {
(*fn)(message)
return
}
fmt.Fprintf(os.Stderr, "[relaykit] SYSTEM ERROR %s\n", message)
}
// Debug reports whether verbose kit diagnostics are enabled. The host sets
// this once at startup (new-api mirrors common.DebugEnabled into it).
var Debug atomic.Bool
+31
View File
@@ -0,0 +1,31 @@
package kitutil
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestErrorHooksRemainDistinct(t *testing.T) {
previousError := logError.Load()
previousSystemError := logSystemError.Load()
t.Cleanup(func() {
logError.Store(previousError)
logSystemError.Store(previousSystemError)
})
var ordinaryMessages []string
var systemMessages []string
SetLogging(nil, func(message string) {
ordinaryMessages = append(ordinaryMessages, message)
})
SetSystemErrorLogging(func(message string) {
systemMessages = append(systemMessages, message)
})
LogError("invalid dto")
LogSystemError("converter failure")
assert.Equal(t, []string{"invalid dto"}, ordinaryMessages)
assert.Equal(t, []string{"converter failure"}, systemMessages)
}
+131
View File
@@ -0,0 +1,131 @@
package kitutil
import (
"net/url"
"regexp"
"strings"
)
var (
maskURLPattern = regexp.MustCompile(`(http|https)://[^\s/$.?#].[^\s]*`)
maskDomainPattern = regexp.MustCompile(`\b(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}\b`)
maskIPPattern = regexp.MustCompile(`\b(?:\d{1,3}\.){3}\d{1,3}\b`)
// maskApiKeyPattern matches patterns like 'api_key:xxx' or "api_key:xxx" to mask the API key value
maskApiKeyPattern = regexp.MustCompile(`(['"]?)api_key:([^\s'"]+)(['"]?)`)
)
// maskHostTail returns the tail parts of a domain/host that should be preserved.
// It keeps 2 parts for likely country-code TLDs (e.g., co.uk, com.cn), otherwise keeps only the TLD.
func maskHostTail(parts []string) []string {
if len(parts) < 2 {
return parts
}
lastPart := parts[len(parts)-1]
secondLastPart := parts[len(parts)-2]
if len(lastPart) == 2 && len(secondLastPart) <= 3 {
// Likely country code TLD like co.uk, com.cn
return []string{secondLastPart, lastPart}
}
return []string{lastPart}
}
// maskHostForURL collapses subdomains and keeps only masked prefix + preserved tail.
// Example: api.openai.com -> ***.com, sub.domain.co.uk -> ***.co.uk
func maskHostForURL(host string) string {
parts := strings.Split(host, ".")
if len(parts) < 2 {
return "***"
}
tail := maskHostTail(parts)
return "***." + strings.Join(tail, ".")
}
// maskHostForPlainDomain masks a plain domain and reflects subdomain depth with multiple ***.
// Example: openai.com -> ***.com, api.openai.com -> ***.***.com, sub.domain.co.uk -> ***.***.co.uk
func maskHostForPlainDomain(domain string) string {
parts := strings.Split(domain, ".")
if len(parts) < 2 {
return domain
}
tail := maskHostTail(parts)
numStars := max(len(parts)-len(tail), 1)
stars := strings.TrimSuffix(strings.Repeat("***.", numStars), ".")
return stars + "." + strings.Join(tail, ".")
}
// MaskSensitiveInfo masks sensitive information like URLs, IPs, and domain names in a string
// Example:
// http://example.com -> http://***.com
// https://api.test.org/v1/users/123?key=secret -> https://***.org/***/***/?key=***
// https://sub.domain.co.uk/path/to/resource -> https://***.co.uk/***/***
// 192.168.1.1 -> ***.***.***.***
// openai.com -> ***.com
// www.openai.com -> ***.***.com
// api.openai.com -> ***.***.com
func MaskSensitiveInfo(str string) string {
// Mask URLs
str = maskURLPattern.ReplaceAllStringFunc(str, func(urlStr string) string {
u, err := url.Parse(urlStr)
if err != nil {
return urlStr
}
host := u.Host
if host == "" {
return urlStr
}
// Mask host with unified logic
maskedHost := maskHostForURL(host)
result := u.Scheme + "://" + maskedHost
// Mask path
if u.Path != "" && u.Path != "/" {
pathParts := strings.Split(strings.Trim(u.Path, "/"), "/")
maskedPathParts := make([]string, len(pathParts))
for i := range pathParts {
if pathParts[i] != "" {
maskedPathParts[i] = "***"
}
}
if len(maskedPathParts) > 0 {
result += "/" + strings.Join(maskedPathParts, "/")
}
} else if u.Path == "/" {
result += "/"
}
// Mask query parameters
if u.RawQuery != "" {
values, err := url.ParseQuery(u.RawQuery)
if err != nil {
// If can't parse query, just mask the whole query string
result += "?***"
} else {
maskedParams := make([]string, 0, len(values))
for key := range values {
maskedParams = append(maskedParams, key+"=***")
}
if len(maskedParams) > 0 {
result += "?" + strings.Join(maskedParams, "&")
}
}
}
return result
})
// Mask domain names without protocol (like openai.com, www.openai.com)
str = maskDomainPattern.ReplaceAllStringFunc(str, func(domain string) string {
return maskHostForPlainDomain(domain)
})
// Mask IP addresses
str = maskIPPattern.ReplaceAllString(str, "***.***.***.***")
// Mask API keys (e.g., "api_key:AIzaSyAAAaUooTUni8AdaOkSRMda30n_Q4vrV70" -> "api_key:***")
str = maskApiKeyPattern.ReplaceAllString(str, "${1}api_key:***${3}")
return str
}
+52
View File
@@ -0,0 +1,52 @@
package kitutil
import (
"fmt"
"strconv"
"strings"
"time"
"github.com/google/uuid"
)
func GetPointer[T any](v T) *T {
return &v
}
func Interface2String(inter any) string {
switch inter.(type) {
case string:
return inter.(string)
case int:
return fmt.Sprintf("%d", inter.(int))
case float64:
return strconv.FormatFloat(inter.(float64), 'f', -1, 64)
case bool:
if inter.(bool) {
return "true"
} else {
return "false"
}
case nil:
return ""
}
return fmt.Sprintf("%v", inter)
}
func String2Int(str string) int {
num, err := strconv.Atoi(str)
if err != nil {
return 0
}
return num
}
func GetUUID() string {
code := uuid.New().String()
code = strings.Replace(code, "-", "", -1)
return code
}
func GetTimestamp() int64 {
return time.Now().Unix()
}