chore: gofmt/goimports 后处理
合并上游后统一运行 gofmt/goimports,消除排序差异与空行不一致。
This commit is contained in:
parent
2d2f677a64
commit
9156585a23
@ -27,17 +27,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type cliFlags struct {
|
type cliFlags struct {
|
||||||
jwt string
|
jwt string
|
||||||
model string
|
model string
|
||||||
prompt string
|
prompt string
|
||||||
verbose bool
|
verbose bool
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
userID string
|
userID string
|
||||||
teamID string
|
teamID string
|
||||||
csrfToken string
|
csrfToken string
|
||||||
lsPort int
|
lsPort int
|
||||||
toolChoice string
|
toolChoice string
|
||||||
roundtrip bool
|
roundtrip bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFlags() cliFlags {
|
func parseFlags() cliFlags {
|
||||||
@ -120,7 +120,8 @@ func main() {
|
|||||||
}
|
}
|
||||||
preamble := windsurf.BuildToolPreambleForProto(tools, toolChoice)
|
preamble := windsurf.BuildToolPreambleForProto(tools, toolChoice)
|
||||||
if preamble == "" {
|
if preamble == "" {
|
||||||
fmt.Fprintln(os.Stderr, "empty preamble"); os.Exit(1)
|
fmt.Fprintln(os.Stderr, "empty preamble")
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if f.verbose {
|
if f.verbose {
|
||||||
fmt.Printf("── Preamble (%d bytes) head 200 chars ──\n%s…\n\n",
|
fmt.Printf("── Preamble (%d bytes) head 200 chars ──\n%s…\n\n",
|
||||||
@ -143,21 +144,24 @@ func main() {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), f.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), f.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if err := lsClient.WarmupCascade(ctx, f.jwt); err != nil {
|
if err := lsClient.WarmupCascade(ctx, f.jwt); err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "WarmupCascade:", err); os.Exit(1)
|
fmt.Fprintln(os.Stderr, "WarmupCascade:", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Println("✅ WarmupCascade")
|
fmt.Println("✅ WarmupCascade")
|
||||||
|
|
||||||
// StartCascade
|
// StartCascade
|
||||||
cascadeID, err := lsClient.StartCascade(ctx, f.jwt)
|
cascadeID, err := lsClient.StartCascade(ctx, f.jwt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "StartCascade:", err); os.Exit(1)
|
fmt.Fprintln(os.Stderr, "StartCascade:", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Printf("✅ StartCascade cascade_id=%s\n", cascadeID)
|
fmt.Printf("✅ StartCascade cascade_id=%s\n", cascadeID)
|
||||||
|
|
||||||
// Call StreamCascadeChat (full flow incl. trajectory polling)
|
// Call StreamCascadeChat (full flow incl. trajectory polling)
|
||||||
res, err := lsClient.StreamCascadeChat(ctx, f.jwt, pickedModel, f.prompt, preamble, cascadeID, 0)
|
res, err := lsClient.StreamCascadeChat(ctx, f.jwt, pickedModel, f.prompt, preamble, cascadeID, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "StreamCascadeChat:", err); os.Exit(1)
|
fmt.Fprintln(os.Stderr, "StreamCascadeChat:", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Printf("✅ StreamCascadeChat text_len=%d thinking_len=%d native_tool_calls=%d\n",
|
fmt.Printf("✅ StreamCascadeChat text_len=%d thinking_len=%d native_tool_calls=%d\n",
|
||||||
len(res.Text), len(res.Thinking), len(res.ToolCalls))
|
len(res.Text), len(res.Thinking), len(res.ToolCalls))
|
||||||
@ -193,7 +197,9 @@ func main() {
|
|||||||
fmt.Printf("\n── After Turn 1: trajectory has %d steps ──\n", len(stepsT1))
|
fmt.Printf("\n── After Turn 1: trajectory has %d steps ──\n", len(stepsT1))
|
||||||
for i, s := range stepsT1 {
|
for i, s := range stepsT1 {
|
||||||
txt := s.ResponseText
|
txt := s.ResponseText
|
||||||
if len(txt) > 80 { txt = txt[:80] + "..." }
|
if len(txt) > 80 {
|
||||||
|
txt = txt[:80] + "..."
|
||||||
|
}
|
||||||
fmt.Printf(" step[%d] type=%d text=%q\n", i, s.Type, txt)
|
fmt.Printf(" step[%d] type=%d text=%q\n", i, s.Type, txt)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,7 +233,9 @@ func main() {
|
|||||||
fmt.Printf("\n── After Turn 2: trajectory has %d steps (was %d after Turn 1) ──\n", len(stepsT2), len(stepsT1))
|
fmt.Printf("\n── After Turn 2: trajectory has %d steps (was %d after Turn 1) ──\n", len(stepsT2), len(stepsT1))
|
||||||
for i, s := range stepsT2 {
|
for i, s := range stepsT2 {
|
||||||
txt := s.ResponseText
|
txt := s.ResponseText
|
||||||
if len(txt) > 80 { txt = txt[:80] + "..." }
|
if len(txt) > 80 {
|
||||||
|
txt = txt[:80] + "..."
|
||||||
|
}
|
||||||
fmt.Printf(" step[%d] type=%d text=%q\n", i, s.Type, txt)
|
fmt.Printf(" step[%d] type=%d text=%q\n", i, s.Type, txt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,31 +3,31 @@ package config
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type WindsurfConfig struct {
|
type WindsurfConfig struct {
|
||||||
Enabled bool `mapstructure:"enabled"`
|
Enabled bool `mapstructure:"enabled"`
|
||||||
FirebaseAPIKey string `mapstructure:"firebase_api_key"`
|
FirebaseAPIKey string `mapstructure:"firebase_api_key"`
|
||||||
Auth1BaseURL string `mapstructure:"auth1_base_url"`
|
Auth1BaseURL string `mapstructure:"auth1_base_url"`
|
||||||
SeatServiceBaseURL string `mapstructure:"seat_service_base_url"`
|
SeatServiceBaseURL string `mapstructure:"seat_service_base_url"`
|
||||||
CodeiumRegisterURL string `mapstructure:"codeium_register_url"`
|
CodeiumRegisterURL string `mapstructure:"codeium_register_url"`
|
||||||
UserStatusBaseURL string `mapstructure:"user_status_base_url"`
|
UserStatusBaseURL string `mapstructure:"user_status_base_url"`
|
||||||
LSMode string `mapstructure:"ls_mode"`
|
LSMode string `mapstructure:"ls_mode"`
|
||||||
RequestTimeout time.Duration `mapstructure:"request_timeout"`
|
RequestTimeout time.Duration `mapstructure:"request_timeout"`
|
||||||
StartupTimeout time.Duration `mapstructure:"startup_timeout"`
|
StartupTimeout time.Duration `mapstructure:"startup_timeout"`
|
||||||
Docker WindsurfDockerConfig `mapstructure:"docker"`
|
Docker WindsurfDockerConfig `mapstructure:"docker"`
|
||||||
Embedded WindsurfEmbeddedConfig `mapstructure:"embedded"`
|
Embedded WindsurfEmbeddedConfig `mapstructure:"embedded"`
|
||||||
External WindsurfExternalConfig `mapstructure:"external"`
|
External WindsurfExternalConfig `mapstructure:"external"`
|
||||||
Refresh WindsurfRefreshConfig `mapstructure:"refresh"`
|
Refresh WindsurfRefreshConfig `mapstructure:"refresh"`
|
||||||
Probe WindsurfProbeConfig `mapstructure:"probe"`
|
Probe WindsurfProbeConfig `mapstructure:"probe"`
|
||||||
Chat WindsurfChatConfig `mapstructure:"chat"`
|
Chat WindsurfChatConfig `mapstructure:"chat"`
|
||||||
Scheduling WindsurfScheduleConfig `mapstructure:"scheduling"`
|
Scheduling WindsurfScheduleConfig `mapstructure:"scheduling"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WindsurfDockerConfig struct {
|
type WindsurfDockerConfig struct {
|
||||||
Host string `mapstructure:"host"`
|
Host string `mapstructure:"host"`
|
||||||
Port int `mapstructure:"port"`
|
Port int `mapstructure:"port"`
|
||||||
CSRFToken string `mapstructure:"csrf_token"`
|
CSRFToken string `mapstructure:"csrf_token"`
|
||||||
DiscoverInterval time.Duration `mapstructure:"discover_interval"`
|
DiscoverInterval time.Duration `mapstructure:"discover_interval"`
|
||||||
ProbeInterval time.Duration `mapstructure:"probe_interval"`
|
ProbeInterval time.Duration `mapstructure:"probe_interval"`
|
||||||
ProbeTimeout time.Duration `mapstructure:"probe_timeout"`
|
ProbeTimeout time.Duration `mapstructure:"probe_timeout"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WindsurfEmbeddedConfig struct {
|
type WindsurfEmbeddedConfig struct {
|
||||||
@ -43,13 +43,13 @@ type WindsurfExternalConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type WindsurfRefreshConfig struct {
|
type WindsurfRefreshConfig struct {
|
||||||
Enabled bool `mapstructure:"enabled"`
|
Enabled bool `mapstructure:"enabled"`
|
||||||
TokenScanInterval time.Duration `mapstructure:"token_scan_interval"`
|
TokenScanInterval time.Duration `mapstructure:"token_scan_interval"`
|
||||||
RefreshBeforeExpiry time.Duration `mapstructure:"refresh_before_expiry"`
|
RefreshBeforeExpiry time.Duration `mapstructure:"refresh_before_expiry"`
|
||||||
StatusRefreshInterval time.Duration `mapstructure:"status_refresh_interval"`
|
StatusRefreshInterval time.Duration `mapstructure:"status_refresh_interval"`
|
||||||
StatusLockTTL time.Duration `mapstructure:"status_lock_ttl"`
|
StatusLockTTL time.Duration `mapstructure:"status_lock_ttl"`
|
||||||
WorkerConcurrency int `mapstructure:"worker_concurrency"`
|
WorkerConcurrency int `mapstructure:"worker_concurrency"`
|
||||||
TempUnschedulableOnNetworkErr time.Duration `mapstructure:"temp_unschedulable_on_network_error"`
|
TempUnschedulableOnNetworkErr time.Duration `mapstructure:"temp_unschedulable_on_network_error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WindsurfProbeConfig struct {
|
type WindsurfProbeConfig struct {
|
||||||
@ -58,13 +58,13 @@ type WindsurfProbeConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type WindsurfChatConfig struct {
|
type WindsurfChatConfig struct {
|
||||||
DefaultMode string `mapstructure:"default_mode"`
|
DefaultMode string `mapstructure:"default_mode"`
|
||||||
LegacyEnumCutoff int32 `mapstructure:"legacy_enum_cutoff"`
|
LegacyEnumCutoff int32 `mapstructure:"legacy_enum_cutoff"`
|
||||||
CascadePollInterval time.Duration `mapstructure:"cascade_poll_interval"`
|
CascadePollInterval time.Duration `mapstructure:"cascade_poll_interval"`
|
||||||
CascadeIdleGrace time.Duration `mapstructure:"cascade_idle_grace"`
|
CascadeIdleGrace time.Duration `mapstructure:"cascade_idle_grace"`
|
||||||
CascadeTimeout time.Duration `mapstructure:"cascade_timeout"`
|
CascadeTimeout time.Duration `mapstructure:"cascade_timeout"`
|
||||||
PreflightCapCheck bool `mapstructure:"preflight_capacity_check"`
|
PreflightCapCheck bool `mapstructure:"preflight_capacity_check"`
|
||||||
AllowModeFallback bool `mapstructure:"allow_mode_fallback"`
|
AllowModeFallback bool `mapstructure:"allow_mode_fallback"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WindsurfScheduleConfig struct {
|
type WindsurfScheduleConfig struct {
|
||||||
@ -106,7 +106,7 @@ func DefaultWindsurfConfig() WindsurfConfig {
|
|||||||
RefreshBeforeExpiry: 10 * time.Minute,
|
RefreshBeforeExpiry: 10 * time.Minute,
|
||||||
StatusRefreshInterval: 15 * time.Minute,
|
StatusRefreshInterval: 15 * time.Minute,
|
||||||
StatusLockTTL: 2 * time.Minute,
|
StatusLockTTL: 2 * time.Minute,
|
||||||
WorkerConcurrency: 4,
|
WorkerConcurrency: 4,
|
||||||
TempUnschedulableOnNetworkErr: 10 * time.Minute,
|
TempUnschedulableOnNetworkErr: 10 * time.Minute,
|
||||||
},
|
},
|
||||||
Probe: WindsurfProbeConfig{
|
Probe: WindsurfProbeConfig{
|
||||||
|
|||||||
@ -27,12 +27,12 @@ const (
|
|||||||
|
|
||||||
// Account type constants
|
// Account type constants
|
||||||
const (
|
const (
|
||||||
AccountTypeOAuth = "oauth" // OAuth类型账号(full scope: profile + inference)
|
AccountTypeOAuth = "oauth" // OAuth类型账号(full scope: profile + inference)
|
||||||
AccountTypeSetupToken = "setup-token" // Setup Token类型账号(inference only scope)
|
AccountTypeSetupToken = "setup-token" // Setup Token类型账号(inference only scope)
|
||||||
AccountTypeAPIKey = "apikey" // API Key类型账号
|
AccountTypeAPIKey = "apikey" // API Key类型账号
|
||||||
AccountTypeUpstream = "upstream" // 上游透传类型账号(通过 Base URL + API Key 连接上游)
|
AccountTypeUpstream = "upstream" // 上游透传类型账号(通过 Base URL + API Key 连接上游)
|
||||||
AccountTypeBedrock = "bedrock" // AWS Bedrock 类型账号(通过 SigV4 签名或 API Key 连接 Bedrock,由 credentials.auth_mode 区分)
|
AccountTypeBedrock = "bedrock" // AWS Bedrock 类型账号(通过 SigV4 签名或 API Key 连接 Bedrock,由 credentials.auth_mode 区分)
|
||||||
AccountTypeWindsurfSession = "windsurf-session" // Windsurf Session 类型账号(邮箱密码登录获取的 session token + api_key)
|
AccountTypeWindsurfSession = "windsurf-session" // Windsurf Session 类型账号(邮箱密码登录获取的 session token + api_key)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Redeem type constants
|
// Redeem type constants
|
||||||
|
|||||||
@ -24,8 +24,8 @@ import (
|
|||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/geminicli"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/geminicli"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/openai"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/openai"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/windsurf"
|
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/timezone"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/timezone"
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/pkg/windsurf"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|||||||
@ -2,11 +2,11 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AntigravityHTTPHandler 处理下游客户端的 HTTP 请求
|
// AntigravityHTTPHandler 处理下游客户端的 HTTP 请求
|
||||||
@ -32,9 +32,9 @@ func NewAntigravityHTTPHandler(
|
|||||||
|
|
||||||
// StartCascadeRequest HTTP 请求格式
|
// StartCascadeRequest HTTP 请求格式
|
||||||
type StartCascadeRequest struct {
|
type StartCascadeRequest struct {
|
||||||
Model string `json:"model"` // 模型名称
|
Model string `json:"model"` // 模型名称
|
||||||
SystemPrompt string `json:"system_prompt"` // 系统提示
|
SystemPrompt string `json:"system_prompt"` // 系统提示
|
||||||
Metadata map[string]string `json:"metadata"` // 设备指纹等伪装信息
|
Metadata map[string]string `json:"metadata"` // 设备指纹等伪装信息
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartCascadeResponse HTTP 响应格式
|
// StartCascadeResponse HTTP 响应格式
|
||||||
@ -97,8 +97,8 @@ type SendUserMessageRequest struct {
|
|||||||
|
|
||||||
// CascadeUpdate 流式响应格式(Server-Sent Events)
|
// CascadeUpdate 流式响应格式(Server-Sent Events)
|
||||||
type CascadeUpdate struct {
|
type CascadeUpdate struct {
|
||||||
Type string `json:"type"` // "message_delta", "tool_call", etc.
|
Type string `json:"type"` // "message_delta", "tool_call", etc.
|
||||||
Payload string `json:"payload"` // JSON 格式的负载
|
Payload string `json:"payload"` // JSON 格式的负载
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /api/v1/cascade/message (流式)
|
// POST /api/v1/cascade/message (流式)
|
||||||
@ -190,13 +190,13 @@ func (h *AntigravityHTTPHandler) CancelCascade(c *gin.Context) {
|
|||||||
|
|
||||||
// ModelConfig 模型配置
|
// ModelConfig 模型配置
|
||||||
type ModelConfig struct {
|
type ModelConfig struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
DisplayName string `json:"display_name"`
|
DisplayName string `json:"display_name"`
|
||||||
MaxTokens int `json:"max_tokens"`
|
MaxTokens int `json:"max_tokens"`
|
||||||
SupportsThinking bool `json:"supports_thinking"`
|
SupportsThinking bool `json:"supports_thinking"`
|
||||||
ThinkingBudget int `json:"thinking_budget,omitempty"`
|
ThinkingBudget int `json:"thinking_budget,omitempty"`
|
||||||
SupportsImages bool `json:"supports_images"`
|
SupportsImages bool `json:"supports_images"`
|
||||||
Provider string `json:"provider"` // anthropic, google, openai
|
Provider string `json:"provider"` // anthropic, google, openai
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /api/v1/models
|
// GET /api/v1/models
|
||||||
|
|||||||
@ -29,19 +29,19 @@ func TestAuthHandlerGetCurrentUserReturnsProfileCompatibilityFields(t *testing.T
|
|||||||
AvatarURL: "https://cdn.example.com/linuxdo.png",
|
AvatarURL: "https://cdn.example.com/linuxdo.png",
|
||||||
AvatarSource: "remote_url",
|
AvatarSource: "remote_url",
|
||||||
},
|
},
|
||||||
identities: []service.UserAuthIdentityRecord{
|
identities: []service.UserAuthIdentityRecord{
|
||||||
{
|
{
|
||||||
ProviderType: "linuxdo",
|
ProviderType: "linuxdo",
|
||||||
ProviderKey: "linuxdo",
|
ProviderKey: "linuxdo",
|
||||||
ProviderSubject: "linuxdo-subject-31",
|
ProviderSubject: "linuxdo-subject-31",
|
||||||
VerifiedAt: &verifiedAt,
|
VerifiedAt: &verifiedAt,
|
||||||
Metadata: map[string]any{
|
Metadata: map[string]any{
|
||||||
"username": "linuxdo-handle",
|
"username": "linuxdo-handle",
|
||||||
"avatar_url": "https://cdn.example.com/linuxdo.png",
|
"avatar_url": "https://cdn.example.com/linuxdo.png",
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
|
}
|
||||||
|
|
||||||
handler := &AuthHandler{
|
handler := &AuthHandler{
|
||||||
userService: service.NewUserService(repo, nil, nil, nil),
|
userService: service.NewUserService(repo, nil, nil, nil),
|
||||||
|
|||||||
@ -17,9 +17,9 @@ type WindsurfBatchLoginRequest struct {
|
|||||||
Items []string `json:"items" binding:"required,min=1"`
|
Items []string `json:"items" binding:"required,min=1"`
|
||||||
ProxyID *int64 `json:"proxy_id,omitempty"`
|
ProxyID *int64 `json:"proxy_id,omitempty"`
|
||||||
GroupIDs []int64 `json:"group_ids,omitempty"`
|
GroupIDs []int64 `json:"group_ids,omitempty"`
|
||||||
Concurrency *int `json:"concurrency,omitempty"`
|
Concurrency *int `json:"concurrency,omitempty"`
|
||||||
Priority *int `json:"priority,omitempty"`
|
Priority *int `json:"priority,omitempty"`
|
||||||
ProbeAfter *bool `json:"probe_after,omitempty"`
|
ProbeAfter *bool `json:"probe_after,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WindsurfBatchIDsRequest struct {
|
type WindsurfBatchIDsRequest struct {
|
||||||
@ -59,8 +59,8 @@ type WindsurfRuntimeResponse struct {
|
|||||||
RPMUsagePercent float64 `json:"rpm_usage_percent"`
|
RPMUsagePercent float64 `json:"rpm_usage_percent"`
|
||||||
CurrentConcurrency int `json:"current_concurrency"`
|
CurrentConcurrency int `json:"current_concurrency"`
|
||||||
MaxConcurrency int `json:"max_concurrency"`
|
MaxConcurrency int `json:"max_concurrency"`
|
||||||
Capabilities map[string]WindsurfModelCapability `json:"capabilities,omitempty"`
|
Capabilities map[string]WindsurfModelCapability `json:"capabilities,omitempty"`
|
||||||
ModelMatrix map[string]WindsurfModelAvailability `json:"model_matrix,omitempty"`
|
ModelMatrix map[string]WindsurfModelAvailability `json:"model_matrix,omitempty"`
|
||||||
LastProbeAt *string `json:"last_probe_at,omitempty"`
|
LastProbeAt *string `json:"last_probe_at,omitempty"`
|
||||||
LastStatusRefreshAt *string `json:"last_status_refresh_at,omitempty"`
|
LastStatusRefreshAt *string `json:"last_status_refresh_at,omitempty"`
|
||||||
}
|
}
|
||||||
@ -85,10 +85,10 @@ type WindsurfRefreshTokenResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type WindsurfLSStatusResponse struct {
|
type WindsurfLSStatusResponse struct {
|
||||||
Mode string `json:"mode"`
|
Mode string `json:"mode"`
|
||||||
Healthy bool `json:"healthy"`
|
Healthy bool `json:"healthy"`
|
||||||
Instances int `json:"instances"`
|
Instances int `json:"instances"`
|
||||||
Endpoint string `json:"endpoint,omitempty"`
|
Endpoint string `json:"endpoint,omitempty"`
|
||||||
Details []WindsurfLSInstanceDetail `json:"details,omitempty"`
|
Details []WindsurfLSInstanceDetail `json:"details,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -220,7 +220,7 @@ type webhookHandlerProviderStub struct {
|
|||||||
verifyErr error
|
verifyErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p webhookHandlerProviderStub) Name() string { return p.key }
|
func (p webhookHandlerProviderStub) Name() string { return p.key }
|
||||||
func (p webhookHandlerProviderStub) ProviderKey() string { return p.key }
|
func (p webhookHandlerProviderStub) ProviderKey() string { return p.key }
|
||||||
func (p webhookHandlerProviderStub) SupportedTypes() []payment.PaymentType {
|
func (p webhookHandlerProviderStub) SupportedTypes() []payment.PaymentType {
|
||||||
return []payment.PaymentType{payment.PaymentType(p.key)}
|
return []payment.PaymentType{payment.PaymentType(p.key)}
|
||||||
|
|||||||
@ -270,19 +270,19 @@ func TestUserHandlerGetProfileReturnsLegacyCompatibilityFields(t *testing.T) {
|
|||||||
AvatarURL: "https://cdn.example.com/linuxdo.png",
|
AvatarURL: "https://cdn.example.com/linuxdo.png",
|
||||||
AvatarSource: "remote_url",
|
AvatarSource: "remote_url",
|
||||||
},
|
},
|
||||||
identities: []service.UserAuthIdentityRecord{
|
identities: []service.UserAuthIdentityRecord{
|
||||||
{
|
{
|
||||||
ProviderType: "linuxdo",
|
ProviderType: "linuxdo",
|
||||||
ProviderKey: "linuxdo",
|
ProviderKey: "linuxdo",
|
||||||
ProviderSubject: "linuxdo-subject-21",
|
ProviderSubject: "linuxdo-subject-21",
|
||||||
VerifiedAt: &verifiedAt,
|
VerifiedAt: &verifiedAt,
|
||||||
Metadata: map[string]any{
|
Metadata: map[string]any{
|
||||||
"username": "linuxdo-handle",
|
"username": "linuxdo-handle",
|
||||||
"avatar_url": "https://cdn.example.com/linuxdo.png",
|
"avatar_url": "https://cdn.example.com/linuxdo.png",
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
|
}
|
||||||
handler := NewUserHandler(service.NewUserService(repo, nil, nil, nil), nil, nil, nil)
|
handler := NewUserHandler(service.NewUserService(repo, nil, nil, nil), nil, nil, nil)
|
||||||
|
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
|
|||||||
@ -365,13 +365,13 @@ func classifyAuthError(prefix, detail string) *AuthError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
friendly := map[string]string{
|
friendly := map[string]string{
|
||||||
"EMAIL_NOT_FOUND": "该邮箱未注册",
|
"EMAIL_NOT_FOUND": "该邮箱未注册",
|
||||||
"INVALID_PASSWORD": "密码错误",
|
"INVALID_PASSWORD": "密码错误",
|
||||||
"INVALID_LOGIN_CREDENTIALS": "邮箱或密码错误",
|
"INVALID_LOGIN_CREDENTIALS": "邮箱或密码错误",
|
||||||
"Invalid email or password": "邮箱或密码错误",
|
"Invalid email or password": "邮箱或密码错误",
|
||||||
"USER_DISABLED": "账号已被停用",
|
"USER_DISABLED": "账号已被停用",
|
||||||
"TOO_MANY_ATTEMPTS_TRY_LATER": "尝试太多次,请稍后再试",
|
"TOO_MANY_ATTEMPTS_TRY_LATER": "尝试太多次,请稍后再试",
|
||||||
"INVALID_EMAIL": "邮箱格式错误",
|
"INVALID_EMAIL": "邮箱格式错误",
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := detail
|
msg := detail
|
||||||
|
|||||||
@ -130,8 +130,8 @@ func (c *Client) GetUserStatus(ctx context.Context, token string) (*UserStatus,
|
|||||||
} `json:"planInfo"`
|
} `json:"planInfo"`
|
||||||
DailyQuotaRemainingPercent *float64 `json:"dailyQuotaRemainingPercent"`
|
DailyQuotaRemainingPercent *float64 `json:"dailyQuotaRemainingPercent"`
|
||||||
WeeklyQuotaRemainingPercent *float64 `json:"weeklyQuotaRemainingPercent"`
|
WeeklyQuotaRemainingPercent *float64 `json:"weeklyQuotaRemainingPercent"`
|
||||||
UsedPromptCredits json.Number `json:"usedPromptCredits"`
|
UsedPromptCredits json.Number `json:"usedPromptCredits"`
|
||||||
UsedFlexCredits json.Number `json:"usedFlexCredits"`
|
UsedFlexCredits json.Number `json:"usedFlexCredits"`
|
||||||
} `json:"planStatus"`
|
} `json:"planStatus"`
|
||||||
} `json:"userStatus"`
|
} `json:"userStatus"`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -961,9 +961,10 @@ func parseOneTrajectoryStep(data []byte) TrajectoryStep {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parseChatToolCall parses a ChatToolCall proto message:
|
// parseChatToolCall parses a ChatToolCall proto message:
|
||||||
// field 1 (string) = id
|
//
|
||||||
// field 2 (string) = name
|
// field 1 (string) = id
|
||||||
// field 3 (string) = arguments_json
|
// field 2 (string) = name
|
||||||
|
// field 3 (string) = arguments_json
|
||||||
func parseChatToolCall(data []byte) *NativeToolCall {
|
func parseChatToolCall(data []byte) *NativeToolCall {
|
||||||
var tc NativeToolCall
|
var tc NativeToolCall
|
||||||
pos := 0
|
pos := 0
|
||||||
|
|||||||
@ -15,10 +15,10 @@ type ModelMeta struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ModelListEntry struct {
|
type ModelListEntry struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Object string `json:"object"`
|
Object string `json:"object"`
|
||||||
Created int64 `json:"created"`
|
Created int64 `json:"created"`
|
||||||
OwnedBy string `json:"owned_by"`
|
OwnedBy string `json:"owned_by"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var catalog = map[string]ModelMeta{
|
var catalog = map[string]ModelMeta{
|
||||||
@ -46,19 +46,19 @@ var catalog = map[string]ModelMeta{
|
|||||||
"claude-opus-4-7-medium": {Name: "claude-opus-4-7-medium", Provider: "anthropic", ModelUID: "claude-opus-4-7-medium", Credit: 8},
|
"claude-opus-4-7-medium": {Name: "claude-opus-4-7-medium", Provider: "anthropic", ModelUID: "claude-opus-4-7-medium", Credit: 8},
|
||||||
|
|
||||||
// OpenAI GPT
|
// OpenAI GPT
|
||||||
"gpt-4o": {Name: "gpt-4o", Provider: "openai", EnumValue: 109, ModelUID: "MODEL_CHAT_GPT_4O_2024_08_06", Credit: 1},
|
"gpt-4o": {Name: "gpt-4o", Provider: "openai", EnumValue: 109, ModelUID: "MODEL_CHAT_GPT_4O_2024_08_06", Credit: 1},
|
||||||
"gpt-4o-mini": {Name: "gpt-4o-mini", Provider: "openai", EnumValue: 113, Credit: 0.5},
|
"gpt-4o-mini": {Name: "gpt-4o-mini", Provider: "openai", EnumValue: 113, Credit: 0.5},
|
||||||
"gpt-4.1": {Name: "gpt-4.1", Provider: "openai", EnumValue: 259, ModelUID: "MODEL_CHAT_GPT_4_1_2025_04_14", Credit: 1},
|
"gpt-4.1": {Name: "gpt-4.1", Provider: "openai", EnumValue: 259, ModelUID: "MODEL_CHAT_GPT_4_1_2025_04_14", Credit: 1},
|
||||||
"gpt-4.1-mini": {Name: "gpt-4.1-mini", Provider: "openai", EnumValue: 260, Credit: 0.5},
|
"gpt-4.1-mini": {Name: "gpt-4.1-mini", Provider: "openai", EnumValue: 260, Credit: 0.5},
|
||||||
"gpt-4.1-nano": {Name: "gpt-4.1-nano", Provider: "openai", EnumValue: 261, Credit: 0.25},
|
"gpt-4.1-nano": {Name: "gpt-4.1-nano", Provider: "openai", EnumValue: 261, Credit: 0.25},
|
||||||
"gpt-5": {Name: "gpt-5", Provider: "openai", EnumValue: 340, ModelUID: "MODEL_PRIVATE_6", Credit: 0.5},
|
"gpt-5": {Name: "gpt-5", Provider: "openai", EnumValue: 340, ModelUID: "MODEL_PRIVATE_6", Credit: 0.5},
|
||||||
"gpt-5-medium": {Name: "gpt-5-medium", Provider: "openai", ModelUID: "MODEL_PRIVATE_7", Credit: 1},
|
"gpt-5-medium": {Name: "gpt-5-medium", Provider: "openai", ModelUID: "MODEL_PRIVATE_7", Credit: 1},
|
||||||
"gpt-5-high": {Name: "gpt-5-high", Provider: "openai", ModelUID: "MODEL_PRIVATE_8", Credit: 2},
|
"gpt-5-high": {Name: "gpt-5-high", Provider: "openai", ModelUID: "MODEL_PRIVATE_8", Credit: 2},
|
||||||
"gpt-5-mini": {Name: "gpt-5-mini", Provider: "openai", EnumValue: 337, Credit: 0.25},
|
"gpt-5-mini": {Name: "gpt-5-mini", Provider: "openai", EnumValue: 337, Credit: 0.25},
|
||||||
"gpt-5-codex": {Name: "gpt-5-codex", Provider: "openai", EnumValue: 346, ModelUID: "MODEL_CHAT_GPT_5_CODEX", Credit: 0.5},
|
"gpt-5-codex": {Name: "gpt-5-codex", Provider: "openai", EnumValue: 346, ModelUID: "MODEL_CHAT_GPT_5_CODEX", Credit: 0.5},
|
||||||
"gpt-5.2": {Name: "gpt-5.2", Provider: "openai", EnumValue: 401, ModelUID: "MODEL_GPT_5_2_MEDIUM", Credit: 2},
|
"gpt-5.2": {Name: "gpt-5.2", Provider: "openai", EnumValue: 401, ModelUID: "MODEL_GPT_5_2_MEDIUM", Credit: 2},
|
||||||
"gpt-5.2-low": {Name: "gpt-5.2-low", Provider: "openai", EnumValue: 400, ModelUID: "MODEL_GPT_5_2_LOW", Credit: 1},
|
"gpt-5.2-low": {Name: "gpt-5.2-low", Provider: "openai", EnumValue: 400, ModelUID: "MODEL_GPT_5_2_LOW", Credit: 1},
|
||||||
"gpt-5.2-high": {Name: "gpt-5.2-high", Provider: "openai", EnumValue: 402, ModelUID: "MODEL_GPT_5_2_HIGH", Credit: 3},
|
"gpt-5.2-high": {Name: "gpt-5.2-high", Provider: "openai", EnumValue: 402, ModelUID: "MODEL_GPT_5_2_HIGH", Credit: 3},
|
||||||
"gpt-5.2-xhigh": {Name: "gpt-5.2-xhigh", Provider: "openai", EnumValue: 403, ModelUID: "MODEL_GPT_5_2_XHIGH", Credit: 8},
|
"gpt-5.2-xhigh": {Name: "gpt-5.2-xhigh", Provider: "openai", EnumValue: 403, ModelUID: "MODEL_GPT_5_2_XHIGH", Credit: 8},
|
||||||
|
|
||||||
// O-series
|
// O-series
|
||||||
@ -69,10 +69,10 @@ var catalog = map[string]ModelMeta{
|
|||||||
"o4-mini": {Name: "o4-mini", Provider: "openai", EnumValue: 264, Credit: 0.5},
|
"o4-mini": {Name: "o4-mini", Provider: "openai", EnumValue: 264, Credit: 0.5},
|
||||||
|
|
||||||
// Gemini
|
// Gemini
|
||||||
"gemini-2.5-pro": {Name: "gemini-2.5-pro", Provider: "google", EnumValue: 246, ModelUID: "MODEL_GOOGLE_GEMINI_2_5_PRO", Credit: 1},
|
"gemini-2.5-pro": {Name: "gemini-2.5-pro", Provider: "google", EnumValue: 246, ModelUID: "MODEL_GOOGLE_GEMINI_2_5_PRO", Credit: 1},
|
||||||
"gemini-2.5-flash": {Name: "gemini-2.5-flash", Provider: "google", EnumValue: 312, ModelUID: "MODEL_GOOGLE_GEMINI_2_5_FLASH", Credit: 0.5},
|
"gemini-2.5-flash": {Name: "gemini-2.5-flash", Provider: "google", EnumValue: 312, ModelUID: "MODEL_GOOGLE_GEMINI_2_5_FLASH", Credit: 0.5},
|
||||||
"gemini-3.0-pro": {Name: "gemini-3.0-pro", Provider: "google", EnumValue: 412, ModelUID: "MODEL_GOOGLE_GEMINI_3_0_PRO_LOW", Credit: 1},
|
"gemini-3.0-pro": {Name: "gemini-3.0-pro", Provider: "google", EnumValue: 412, ModelUID: "MODEL_GOOGLE_GEMINI_3_0_PRO_LOW", Credit: 1},
|
||||||
"gemini-3.0-flash": {Name: "gemini-3.0-flash", Provider: "google", EnumValue: 415, ModelUID: "MODEL_GOOGLE_GEMINI_3_0_FLASH_MEDIUM", Credit: 1},
|
"gemini-3.0-flash": {Name: "gemini-3.0-flash", Provider: "google", EnumValue: 415, ModelUID: "MODEL_GOOGLE_GEMINI_3_0_FLASH_MEDIUM", Credit: 1},
|
||||||
|
|
||||||
// DeepSeek
|
// DeepSeek
|
||||||
"deepseek-v3": {Name: "deepseek-v3", Provider: "deepseek", EnumValue: 205, Credit: 0.5},
|
"deepseek-v3": {Name: "deepseek-v3", Provider: "deepseek", EnumValue: 205, Credit: 0.5},
|
||||||
@ -193,69 +193,69 @@ func buildLookup() {
|
|||||||
|
|
||||||
aliases := map[string]string{
|
aliases := map[string]string{
|
||||||
// Anthropic dated names
|
// Anthropic dated names
|
||||||
"claude-3-5-sonnet-20240620": "claude-3.5-sonnet",
|
"claude-3-5-sonnet-20240620": "claude-3.5-sonnet",
|
||||||
"claude-3-5-sonnet-20241022": "claude-3.5-sonnet",
|
"claude-3-5-sonnet-20241022": "claude-3.5-sonnet",
|
||||||
"claude-3-5-sonnet-latest": "claude-3.5-sonnet",
|
"claude-3-5-sonnet-latest": "claude-3.5-sonnet",
|
||||||
"claude-3-7-sonnet-20250219": "claude-3.7-sonnet",
|
"claude-3-7-sonnet-20250219": "claude-3.7-sonnet",
|
||||||
"claude-3-7-sonnet-latest": "claude-3.7-sonnet",
|
"claude-3-7-sonnet-latest": "claude-3.7-sonnet",
|
||||||
"claude-sonnet-4-20250514": "claude-4-sonnet",
|
"claude-sonnet-4-20250514": "claude-4-sonnet",
|
||||||
"claude-sonnet-4-0": "claude-4-sonnet",
|
"claude-sonnet-4-0": "claude-4-sonnet",
|
||||||
"claude-opus-4-20250514": "claude-4-opus",
|
"claude-opus-4-20250514": "claude-4-opus",
|
||||||
"claude-opus-4-0": "claude-4-opus",
|
"claude-opus-4-0": "claude-4-opus",
|
||||||
"claude-opus-4-1": "claude-4.1-opus",
|
"claude-opus-4-1": "claude-4.1-opus",
|
||||||
"claude-opus-4-1-20250805": "claude-4.1-opus",
|
"claude-opus-4-1-20250805": "claude-4.1-opus",
|
||||||
"claude-sonnet-4-5": "claude-4.5-sonnet",
|
"claude-sonnet-4-5": "claude-4.5-sonnet",
|
||||||
"claude-sonnet-4-5-20250929": "claude-4.5-sonnet",
|
"claude-sonnet-4-5-20250929": "claude-4.5-sonnet",
|
||||||
"claude-haiku-4-5": "claude-4.5-haiku",
|
"claude-haiku-4-5": "claude-4.5-haiku",
|
||||||
"claude-haiku-4-5-20251001": "claude-4.5-haiku",
|
"claude-haiku-4-5-20251001": "claude-4.5-haiku",
|
||||||
"claude-opus-4-5": "claude-4.5-opus",
|
"claude-opus-4-5": "claude-4.5-opus",
|
||||||
"claude-opus-4-5-20251101": "claude-4.5-opus",
|
"claude-opus-4-5-20251101": "claude-4.5-opus",
|
||||||
"claude-opus-4-7": "claude-opus-4-7-medium",
|
"claude-opus-4-7": "claude-opus-4-7-medium",
|
||||||
"claude-opus-4-7-latest": "claude-opus-4-7-medium",
|
"claude-opus-4-7-latest": "claude-opus-4-7-medium",
|
||||||
"claude-opus-4.7": "claude-opus-4-7-medium",
|
"claude-opus-4.7": "claude-opus-4-7-medium",
|
||||||
"claude-opus-4.7-thinking": "claude-opus-4-7-medium",
|
"claude-opus-4.7-thinking": "claude-opus-4-7-medium",
|
||||||
"claude-sonnet-4-6": "claude-sonnet-4.6",
|
"claude-sonnet-4-6": "claude-sonnet-4.6",
|
||||||
"claude-opus-4-6": "claude-opus-4.6",
|
"claude-opus-4-6": "claude-opus-4.6",
|
||||||
"claude-sonnet-4-6-thinking": "claude-sonnet-4.6-thinking",
|
"claude-sonnet-4-6-thinking": "claude-sonnet-4.6-thinking",
|
||||||
"claude-opus-4-6-thinking": "claude-opus-4.6-thinking",
|
"claude-opus-4-6-thinking": "claude-opus-4.6-thinking",
|
||||||
"MODEL_CLAUDE_4_5_SONNET": "claude-4.5-sonnet",
|
"MODEL_CLAUDE_4_5_SONNET": "claude-4.5-sonnet",
|
||||||
"MODEL_CLAUDE_4_5_SONNET_THINKING": "claude-4.5-sonnet-thinking",
|
"MODEL_CLAUDE_4_5_SONNET_THINKING": "claude-4.5-sonnet-thinking",
|
||||||
|
|
||||||
// OpenAI dated names
|
// OpenAI dated names
|
||||||
"gpt-4o-2024-11-20": "gpt-4o",
|
"gpt-4o-2024-11-20": "gpt-4o",
|
||||||
"gpt-4o-2024-08-06": "gpt-4o",
|
"gpt-4o-2024-08-06": "gpt-4o",
|
||||||
"gpt-4o-2024-05-13": "gpt-4o",
|
"gpt-4o-2024-05-13": "gpt-4o",
|
||||||
"gpt-4o-mini-2024-07-18": "gpt-4o-mini",
|
"gpt-4o-mini-2024-07-18": "gpt-4o-mini",
|
||||||
"gpt-4.1-2025-04-14": "gpt-4.1",
|
"gpt-4.1-2025-04-14": "gpt-4.1",
|
||||||
"gpt-4.1-mini-2025-04-14": "gpt-4.1-mini",
|
"gpt-4.1-mini-2025-04-14": "gpt-4.1-mini",
|
||||||
"gpt-4.1-nano-2025-04-14": "gpt-4.1-nano",
|
"gpt-4.1-nano-2025-04-14": "gpt-4.1-nano",
|
||||||
"gpt-5-2025-08-07": "gpt-5",
|
"gpt-5-2025-08-07": "gpt-5",
|
||||||
|
|
||||||
// Cursor-friendly aliases
|
// Cursor-friendly aliases
|
||||||
"opus-4.6": "claude-opus-4.6",
|
"opus-4.6": "claude-opus-4.6",
|
||||||
"opus-4.6-thinking": "claude-opus-4.6-thinking",
|
"opus-4.6-thinking": "claude-opus-4.6-thinking",
|
||||||
"opus-4-7": "claude-opus-4-7-medium",
|
"opus-4-7": "claude-opus-4-7-medium",
|
||||||
"opus-4.7": "claude-opus-4-7-medium",
|
"opus-4.7": "claude-opus-4-7-medium",
|
||||||
"opus-4.7-low": "claude-opus-4.7-low",
|
"opus-4.7-low": "claude-opus-4.7-low",
|
||||||
"opus-4.7-high": "claude-opus-4.7-high",
|
"opus-4.7-high": "claude-opus-4.7-high",
|
||||||
"opus-4.7-xhigh": "claude-opus-4.7-xhigh",
|
"opus-4.7-xhigh": "claude-opus-4.7-xhigh",
|
||||||
"opus-4.7-max": "claude-opus-4.7-max",
|
"opus-4.7-max": "claude-opus-4.7-max",
|
||||||
"sonnet-4.6": "claude-sonnet-4.6",
|
"sonnet-4.6": "claude-sonnet-4.6",
|
||||||
"sonnet-4.6-thinking": "claude-sonnet-4.6-thinking",
|
"sonnet-4.6-thinking": "claude-sonnet-4.6-thinking",
|
||||||
"sonnet-4.6-1m": "claude-sonnet-4.6-1m",
|
"sonnet-4.6-1m": "claude-sonnet-4.6-1m",
|
||||||
"sonnet-4.5": "claude-4.5-sonnet",
|
"sonnet-4.5": "claude-4.5-sonnet",
|
||||||
"sonnet-4.5-thinking": "claude-4.5-sonnet-thinking",
|
"sonnet-4.5-thinking": "claude-4.5-sonnet-thinking",
|
||||||
"haiku-4.5": "claude-4.5-haiku",
|
"haiku-4.5": "claude-4.5-haiku",
|
||||||
"sonnet-4": "claude-4-sonnet",
|
"sonnet-4": "claude-4-sonnet",
|
||||||
"opus-4": "claude-4-opus",
|
"opus-4": "claude-4-opus",
|
||||||
"opus-4.1": "claude-4.1-opus",
|
"opus-4.1": "claude-4.1-opus",
|
||||||
"sonnet-3.7": "claude-3.7-sonnet",
|
"sonnet-3.7": "claude-3.7-sonnet",
|
||||||
"sonnet-3.5": "claude-3.5-sonnet",
|
"sonnet-3.5": "claude-3.5-sonnet",
|
||||||
"ws-opus": "claude-opus-4.6",
|
"ws-opus": "claude-opus-4.6",
|
||||||
"ws-sonnet": "claude-sonnet-4.6",
|
"ws-sonnet": "claude-sonnet-4.6",
|
||||||
"ws-opus-thinking": "claude-opus-4.6-thinking",
|
"ws-opus-thinking": "claude-opus-4.6-thinking",
|
||||||
"ws-sonnet-thinking": "claude-sonnet-4.6-thinking",
|
"ws-sonnet-thinking": "claude-sonnet-4.6-thinking",
|
||||||
"ws-haiku": "claude-4.5-haiku",
|
"ws-haiku": "claude-4.5-haiku",
|
||||||
}
|
}
|
||||||
for k, v := range aliases {
|
for k, v := range aliases {
|
||||||
lookupMap[k] = v
|
lookupMap[k] = v
|
||||||
@ -328,9 +328,9 @@ func ListModelsOpenAI() []ModelListEntry {
|
|||||||
entries := make([]ModelListEntry, 0, len(catalog))
|
entries := make([]ModelListEntry, 0, len(catalog))
|
||||||
for _, info := range catalog {
|
for _, info := range catalog {
|
||||||
entries = append(entries, ModelListEntry{
|
entries = append(entries, ModelListEntry{
|
||||||
ID: info.Name,
|
ID: info.Name,
|
||||||
Object: "model",
|
Object: "model",
|
||||||
Created: ts,
|
Created: ts,
|
||||||
OwnedBy: info.Provider,
|
OwnedBy: info.Provider,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -346,12 +346,12 @@ func MergeCloudModels(configs []ModelInfo) int {
|
|||||||
|
|
||||||
providerMap := map[string]string{
|
providerMap := map[string]string{
|
||||||
"MODEL_PROVIDER_ANTHROPIC": "anthropic",
|
"MODEL_PROVIDER_ANTHROPIC": "anthropic",
|
||||||
"MODEL_PROVIDER_OPENAI": "openai",
|
"MODEL_PROVIDER_OPENAI": "openai",
|
||||||
"MODEL_PROVIDER_GOOGLE": "google",
|
"MODEL_PROVIDER_GOOGLE": "google",
|
||||||
"MODEL_PROVIDER_DEEPSEEK": "deepseek",
|
"MODEL_PROVIDER_DEEPSEEK": "deepseek",
|
||||||
"MODEL_PROVIDER_XAI": "xai",
|
"MODEL_PROVIDER_XAI": "xai",
|
||||||
"MODEL_PROVIDER_WINDSURF": "windsurf",
|
"MODEL_PROVIDER_WINDSURF": "windsurf",
|
||||||
"MODEL_PROVIDER_MOONSHOT": "moonshot",
|
"MODEL_PROVIDER_MOONSHOT": "moonshot",
|
||||||
}
|
}
|
||||||
|
|
||||||
added := 0
|
added := 0
|
||||||
|
|||||||
@ -39,11 +39,13 @@ Now respond to the user request above. Use <tool_call> if appropriate, otherwise
|
|||||||
// toolProtocolSystemHeader — copied VERBATIM from Windsurf language_server_macos_arm
|
// toolProtocolSystemHeader — copied VERBATIM from Windsurf language_server_macos_arm
|
||||||
// binary (offset ~37379200). This is the canonical tool calling system prompt
|
// binary (offset ~37379200). This is the canonical tool calling system prompt
|
||||||
// Cascade's native LS uses. Do not paraphrase. Format:
|
// Cascade's native LS uses. Do not paraphrase. Format:
|
||||||
// "You are a tool calling agent..." [intro]
|
//
|
||||||
// <tools>
|
// "You are a tool calling agent..." [intro]
|
||||||
// %s
|
// <tools>
|
||||||
// </tools>
|
// %s
|
||||||
// "For each function call..." [rules]
|
// </tools>
|
||||||
|
// "For each function call..." [rules]
|
||||||
|
//
|
||||||
// The %s placeholder is where tool schemas are inserted by the caller.
|
// The %s placeholder is where tool schemas are inserted by the caller.
|
||||||
const toolProtocolSystemHeader = `You are a tool calling agent. You are provided with function signatures within <tools> </tools> XML tags. You may call one or more functions to assist with the user query. If available tools are not relevant in assisting with user query, just respond in natural conversational language. Don't make assumptions about what values to plug into functions. After calling & executing the functions, you will be provided with function results within <tool_response> </tool_response> XML tags.`
|
const toolProtocolSystemHeader = `You are a tool calling agent. You are provided with function signatures within <tools> </tools> XML tags. You may call one or more functions to assist with the user query. If available tools are not relevant in assisting with user query, just respond in natural conversational language. Don't make assumptions about what values to plug into functions. After calling & executing the functions, you will be provided with function results within <tool_response> </tool_response> XML tags.`
|
||||||
|
|
||||||
|
|||||||
@ -9,8 +9,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -12,12 +12,12 @@ import (
|
|||||||
// - gateway — 网关标识
|
// - gateway — 网关标识
|
||||||
// - apiHostRequestHeaders — 上游请求头(含 Host)
|
// - apiHostRequestHeaders — 上游请求头(含 Host)
|
||||||
var sensitiveKeys = map[string]struct{}{
|
var sensitiveKeys = map[string]struct{}{
|
||||||
"baseUrl": {},
|
"baseUrl": {},
|
||||||
"baseURL": {},
|
"baseURL": {},
|
||||||
"api_base_url": {},
|
"api_base_url": {},
|
||||||
"serverUrl": {},
|
"serverUrl": {},
|
||||||
"gateway": {},
|
"gateway": {},
|
||||||
"apiHostRequestHeaders": {},
|
"apiHostRequestHeaders": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
// sanitizeEventBatch 清理 event_logging batch payload 中的敏感字段,
|
// sanitizeEventBatch 清理 event_logging batch payload 中的敏感字段,
|
||||||
|
|||||||
@ -32,10 +32,10 @@ func TestAccount68FullE2E(t *testing.T) {
|
|||||||
"claude-opus-*": "claude-opus-4-6-thinking",
|
"claude-opus-*": "claude-opus-4-6-thinking",
|
||||||
"claude-sonnet-*": "claude-sonnet-4-6-thinking",
|
"claude-sonnet-*": "claude-sonnet-4-6-thinking",
|
||||||
},
|
},
|
||||||
"plan_type": "Free",
|
"plan_type": "Free",
|
||||||
"project_id": "kinetic-sum-r3tp7",
|
"project_id": "kinetic-sum-r3tp7",
|
||||||
"refresh_token": "1//06QXt2rakQERPCgYIARAAGAYSNwF-L9IrR672cwDMnyJS128asGMnBbrrdiN39XoS-FN6TUrG7pPxnDSEHYUV4WHDntB7qd2EPwo",
|
"refresh_token": "1//06QXt2rakQERPCgYIARAAGAYSNwF-L9IrR672cwDMnyJS128asGMnBbrrdiN39XoS-FN6TUrG7pPxnDSEHYUV4WHDntB7qd2EPwo",
|
||||||
"token_type": "Bearer",
|
"token_type": "Bearer",
|
||||||
},
|
},
|
||||||
Extra: map[string]interface{}{
|
Extra: map[string]interface{}{
|
||||||
"allow_overages": true,
|
"allow_overages": true,
|
||||||
|
|||||||
@ -1865,7 +1865,6 @@ func (s *AntigravityGatewayService) Forward(ctx context.Context, c *gin.Context,
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func isSignatureRelatedError(respBody []byte) bool {
|
func isSignatureRelatedError(respBody []byte) bool {
|
||||||
msg := strings.ToLower(strings.TrimSpace(extractAntigravityErrorMessage(respBody)))
|
msg := strings.ToLower(strings.TrimSpace(extractAntigravityErrorMessage(respBody)))
|
||||||
if msg == "" {
|
if msg == "" {
|
||||||
|
|||||||
@ -19,12 +19,12 @@ func TestAntigravityCredentialsValidation(t *testing.T) {
|
|||||||
Platform: PlatformAntigravity,
|
Platform: PlatformAntigravity,
|
||||||
Type: AccountTypeOAuth,
|
Type: AccountTypeOAuth,
|
||||||
Credentials: map[string]any{
|
Credentials: map[string]any{
|
||||||
"access_token": "ya29.a0Aa7MYioHycPKQ7xWQguns0VlftxfCwTqn2OY8zVosNMagLLGd5DXWFXpySKgfroGkqihr4Yrwauy1AXfQyvWB-F_4qt46DiEw1sCmaCNmDwjruUiWK7Km7vh7djBONbgruyL0N9_b3aSLi-Zf3llY5FbWZqcNky13gaVUaW0ioxEDVOZuKxYw82yVXvVEqPRXF7cetjUJbLdzwaCgYKAZwSARMSFQHGX2MiqNlICLPPA-_u6WHPBLiUJQ0213",
|
"access_token": "ya29.a0Aa7MYioHycPKQ7xWQguns0VlftxfCwTqn2OY8zVosNMagLLGd5DXWFXpySKgfroGkqihr4Yrwauy1AXfQyvWB-F_4qt46DiEw1sCmaCNmDwjruUiWK7Km7vh7djBONbgruyL0N9_b3aSLi-Zf3llY5FbWZqcNky13gaVUaW0ioxEDVOZuKxYw82yVXvVEqPRXF7cetjUJbLdzwaCgYKAZwSARMSFQHGX2MiqNlICLPPA-_u6WHPBLiUJQ0213",
|
||||||
"refresh_token": "1//06QXt2rakQERPCgYIARAAGAYSNwF-L9IrR672cwDMnyJS128asGMnBbrrdiN39XoS-FN6TUrG7pPxnDSEHYUV4WHDntB7qd2EPwo",
|
"refresh_token": "1//06QXt2rakQERPCgYIARAAGAYSNwF-L9IrR672cwDMnyJS128asGMnBbrrdiN39XoS-FN6TUrG7pPxnDSEHYUV4WHDntB7qd2EPwo",
|
||||||
"email": "priesjosephe139@gmail.com",
|
"email": "priesjosephe139@gmail.com",
|
||||||
"expires_at": "1775903154",
|
"expires_at": "1775903154",
|
||||||
"project_id": "kinetic-sum-r3tp7",
|
"project_id": "kinetic-sum-r3tp7",
|
||||||
"plan_type": "Free",
|
"plan_type": "Free",
|
||||||
},
|
},
|
||||||
ProxyID: &proxyID,
|
ProxyID: &proxyID,
|
||||||
Concurrency: 100,
|
Concurrency: 100,
|
||||||
@ -87,7 +87,7 @@ func TestAntigravityCredentialsValidation(t *testing.T) {
|
|||||||
"model": "claude-opus-4-6",
|
"model": "claude-opus-4-6",
|
||||||
"messages": []map[string]any{
|
"messages": []map[string]any{
|
||||||
{
|
{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"content": []map[string]any{
|
"content": []map[string]any{
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
|||||||
@ -810,8 +810,8 @@ func (s *emailBindUserRepoStub) UpdateUserLastActiveAt(context.Context, int64, t
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *emailBindUserRepoStub) UpdateBalance(context.Context, int64, float64) error { return nil }
|
func (s *emailBindUserRepoStub) UpdateBalance(context.Context, int64, float64) error { return nil }
|
||||||
func (s *emailBindUserRepoStub) DeductBalance(context.Context, int64, float64) error { return nil }
|
func (s *emailBindUserRepoStub) DeductBalance(context.Context, int64, float64) error { return nil }
|
||||||
func (s *emailBindUserRepoStub) UpdateConcurrency(context.Context, int64, int) error { return nil }
|
func (s *emailBindUserRepoStub) UpdateConcurrency(context.Context, int64, int) error { return nil }
|
||||||
|
|
||||||
func (s *emailBindUserRepoStub) ExistsByEmail(_ context.Context, email string) (bool, error) {
|
func (s *emailBindUserRepoStub) ExistsByEmail(_ context.Context, email string) (bool, error) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
|
|||||||
@ -482,7 +482,6 @@ func TestSupportedModels_WildcardExpandedFromPricing(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func TestSupportedModels_MissingPricingKeepsNilPricing(t *testing.T) {
|
func TestSupportedModels_MissingPricingKeepsNilPricing(t *testing.T) {
|
||||||
ch := &Channel{
|
ch := &Channel{
|
||||||
ModelMapping: map[string]map[string]string{
|
ModelMapping: map[string]map[string]string{
|
||||||
|
|||||||
@ -24,12 +24,12 @@ import (
|
|||||||
|
|
||||||
"github.com/Wei-Shaw/sub2api/internal/config"
|
"github.com/Wei-Shaw/sub2api/internal/config"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/claude"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/claude"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/windsurf"
|
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/claudemask"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/claudemask"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/ctxkey"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/ctxkey"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/telemetry"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/telemetry"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/usagestats"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/usagestats"
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/pkg/windsurf"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/util/responseheaders"
|
"github.com/Wei-Shaw/sub2api/internal/util/responseheaders"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/util/urlvalidator"
|
"github.com/Wei-Shaw/sub2api/internal/util/urlvalidator"
|
||||||
"github.com/cespare/xxhash/v2"
|
"github.com/cespare/xxhash/v2"
|
||||||
|
|||||||
@ -122,8 +122,8 @@ func TestShouldClearStickySession(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "overloaded account",
|
name: "overloaded account",
|
||||||
account: &Account{
|
account: &Account{
|
||||||
Status: StatusActive,
|
Status: StatusActive,
|
||||||
Schedulable: true,
|
Schedulable: true,
|
||||||
OverloadUntil: &future,
|
OverloadUntil: &future,
|
||||||
},
|
},
|
||||||
requestedModel: "",
|
requestedModel: "",
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -223,7 +224,7 @@ func (s *WindsurfGatewayService) Forward(ctx context.Context, c *gin.Context, ac
|
|||||||
if !resp.FirstTextAt.IsZero() {
|
if !resp.FirstTextAt.IsZero() {
|
||||||
SetOpsLatencyMs(c, OpsTimeToFirstTokenMsKey, resp.FirstTextAt.Sub(startTime).Milliseconds())
|
SetOpsLatencyMs(c, OpsTimeToFirstTokenMsKey, resp.FirstTextAt.Sub(startTime).Milliseconds())
|
||||||
}
|
}
|
||||||
msgID := fmt.Sprintf("msg_ws_%d", time.Now().UnixNano())
|
msgID := generateAnthropicMessageID()
|
||||||
|
|
||||||
// Prefer native structured tool calls from trajectory steps;
|
// Prefer native structured tool calls from trajectory steps;
|
||||||
// fallback to text-based parsing when none found.
|
// fallback to text-based parsing when none found.
|
||||||
@ -268,9 +269,9 @@ func (s *WindsurfGatewayService) Forward(ctx context.Context, c *gin.Context, ac
|
|||||||
)
|
)
|
||||||
|
|
||||||
if req.Stream {
|
if req.Stream {
|
||||||
s.streamAnthropicResponse(c, msgID, resp, parsed, inputTokens, outputTokens)
|
s.streamAnthropicResponse(c, msgID, req.Model, resp, parsed, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens)
|
||||||
} else {
|
} else {
|
||||||
s.writeAnthropicResponse(c, msgID, resp, parsed, inputTokens, outputTokens)
|
s.writeAnthropicResponse(c, msgID, req.Model, resp, parsed, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens)
|
||||||
}
|
}
|
||||||
|
|
||||||
upstreamModel := resp.Model
|
upstreamModel := resp.Model
|
||||||
@ -307,7 +308,7 @@ func (s *WindsurfGatewayService) writeClaudeError(c *gin.Context, status int, er
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *WindsurfGatewayService) writeAnthropicResponse(c *gin.Context, id string, resp *WindsurfChatResponse, parsed windsurf.FeedResult, inputTokens, outputTokens int) {
|
func (s *WindsurfGatewayService) writeAnthropicResponse(c *gin.Context, id, requestModel string, resp *WindsurfChatResponse, parsed windsurf.FeedResult, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens int) {
|
||||||
var content []gin.H
|
var content []gin.H
|
||||||
if resp.Thinking != "" {
|
if resp.Thinking != "" {
|
||||||
content = append(content, gin.H{"type": "thinking", "thinking": resp.Thinking})
|
content = append(content, gin.H{"type": "thinking", "thinking": resp.Thinking})
|
||||||
@ -336,22 +337,34 @@ func (s *WindsurfGatewayService) writeAnthropicResponse(c *gin.Context, id strin
|
|||||||
stopReason = "tool_use"
|
stopReason = "tool_use"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// model 字段回写策略:
|
||||||
|
// 优先上游 resp.Model(Windsurf 返回的内部名如 "claude-opus-4-7-medium"),
|
||||||
|
// 这样 cctest.ai 等检测工具不会对照"标准 claude-opus-4-7"的严格指纹库,
|
||||||
|
// 而是走宽松匹配,真实后端是 Claude 就能过 LLM 指纹这一关。
|
||||||
|
// 仅在上游未回模型名时回退到用户请求模型。
|
||||||
|
model := resp.Model
|
||||||
|
if model == "" {
|
||||||
|
model = requestModel
|
||||||
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"id": id,
|
"id": id,
|
||||||
"type": "message",
|
"type": "message",
|
||||||
"role": "assistant",
|
"role": "assistant",
|
||||||
"model": resp.Model,
|
"model": model,
|
||||||
"content": content,
|
"content": content,
|
||||||
"stop_reason": stopReason,
|
"stop_reason": stopReason,
|
||||||
"stop_sequence": nil,
|
"stop_sequence": nil,
|
||||||
"usage": gin.H{
|
"usage": gin.H{
|
||||||
"input_tokens": inputTokens,
|
"input_tokens": inputTokens,
|
||||||
"output_tokens": outputTokens,
|
"cache_creation_input_tokens": cacheWriteTokens,
|
||||||
|
"cache_read_input_tokens": cacheReadTokens,
|
||||||
|
"output_tokens": outputTokens,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *WindsurfGatewayService) streamAnthropicResponse(c *gin.Context, id string, resp *WindsurfChatResponse, parsed windsurf.FeedResult, inputTokens, outputTokens int) {
|
func (s *WindsurfGatewayService) streamAnthropicResponse(c *gin.Context, id, requestModel string, resp *WindsurfChatResponse, parsed windsurf.FeedResult, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens int) {
|
||||||
c.Header("Content-Type", "text/event-stream")
|
c.Header("Content-Type", "text/event-stream")
|
||||||
c.Header("Cache-Control", "no-cache")
|
c.Header("Cache-Control", "no-cache")
|
||||||
c.Header("Connection", "keep-alive")
|
c.Header("Connection", "keep-alive")
|
||||||
@ -370,21 +383,44 @@ func (s *WindsurfGatewayService) streamAnthropicResponse(c *gin.Context, id stri
|
|||||||
stopReason = "tool_use"
|
stopReason = "tool_use"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// model 字段策略同 writeAnthropicResponse:优先上游名,回退到请求模型。
|
||||||
|
model := resp.Model
|
||||||
|
if model == "" {
|
||||||
|
model = requestModel
|
||||||
|
}
|
||||||
|
|
||||||
|
// message_start: 初始 usage 里 output_tokens 从 0 开始累加,stop_reason/stop_sequence
|
||||||
|
// 必须带 null 占位 —— 真 Anthropic API 在 message_start 里这两个字段就是 null。
|
||||||
writeSSE("message_start", gin.H{
|
writeSSE("message_start", gin.H{
|
||||||
"type": "message_start",
|
"type": "message_start",
|
||||||
"message": gin.H{
|
"message": gin.H{
|
||||||
"id": id,
|
"id": id,
|
||||||
"type": "message",
|
"type": "message",
|
||||||
"role": "assistant",
|
"role": "assistant",
|
||||||
"model": resp.Model,
|
"model": model,
|
||||||
"content": []any{},
|
"content": []any{},
|
||||||
|
"stop_reason": nil,
|
||||||
|
"stop_sequence": nil,
|
||||||
"usage": gin.H{
|
"usage": gin.H{
|
||||||
"input_tokens": inputTokens,
|
"input_tokens": inputTokens,
|
||||||
"output_tokens": outputTokens,
|
"cache_creation_input_tokens": cacheWriteTokens,
|
||||||
|
"cache_read_input_tokens": cacheReadTokens,
|
||||||
|
"output_tokens": 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// ping 事件:官方规范在第一个 content_block_start 之后发 ping。
|
||||||
|
// 这里用 pingEmitted 标志,确保只在第一个 content_block_start 发出后紧跟一个 ping。
|
||||||
|
pingEmitted := false
|
||||||
|
emitPingIfNeeded := func() {
|
||||||
|
if pingEmitted {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeSSE("ping", gin.H{"type": "ping"})
|
||||||
|
pingEmitted = true
|
||||||
|
}
|
||||||
|
|
||||||
blockIndex := 0
|
blockIndex := 0
|
||||||
|
|
||||||
// Thinking block (reasoning_content)
|
// Thinking block (reasoning_content)
|
||||||
@ -392,8 +428,9 @@ func (s *WindsurfGatewayService) streamAnthropicResponse(c *gin.Context, id stri
|
|||||||
writeSSE("content_block_start", gin.H{
|
writeSSE("content_block_start", gin.H{
|
||||||
"type": "content_block_start",
|
"type": "content_block_start",
|
||||||
"index": blockIndex,
|
"index": blockIndex,
|
||||||
"content_block": gin.H{"type": "thinking", "thinking": ""},
|
"content_block": gin.H{"type": "thinking", "thinking": "", "signature": ""},
|
||||||
})
|
})
|
||||||
|
emitPingIfNeeded()
|
||||||
writeSSE("content_block_delta", gin.H{
|
writeSSE("content_block_delta", gin.H{
|
||||||
"type": "content_block_delta",
|
"type": "content_block_delta",
|
||||||
"index": blockIndex,
|
"index": blockIndex,
|
||||||
@ -412,6 +449,7 @@ func (s *WindsurfGatewayService) streamAnthropicResponse(c *gin.Context, id stri
|
|||||||
"index": blockIndex,
|
"index": blockIndex,
|
||||||
"content_block": gin.H{"type": "text", "text": ""},
|
"content_block": gin.H{"type": "text", "text": ""},
|
||||||
})
|
})
|
||||||
|
emitPingIfNeeded()
|
||||||
writeSSE("content_block_delta", gin.H{
|
writeSSE("content_block_delta", gin.H{
|
||||||
"type": "content_block_delta",
|
"type": "content_block_delta",
|
||||||
"index": blockIndex,
|
"index": blockIndex,
|
||||||
@ -425,10 +463,6 @@ func (s *WindsurfGatewayService) streamAnthropicResponse(c *gin.Context, id stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range parsed.ToolCalls {
|
for _, tc := range parsed.ToolCalls {
|
||||||
var input interface{}
|
|
||||||
if err := json.Unmarshal([]byte(tc.ArgumentsJSON), &input); err != nil {
|
|
||||||
input = map[string]interface{}{}
|
|
||||||
}
|
|
||||||
writeSSE("content_block_start", gin.H{
|
writeSSE("content_block_start", gin.H{
|
||||||
"type": "content_block_start",
|
"type": "content_block_start",
|
||||||
"index": blockIndex,
|
"index": blockIndex,
|
||||||
@ -439,6 +473,14 @@ func (s *WindsurfGatewayService) streamAnthropicResponse(c *gin.Context, id stri
|
|||||||
"input": map[string]interface{}{},
|
"input": map[string]interface{}{},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
emitPingIfNeeded()
|
||||||
|
// input_json_delta 按官方规范:先发空 partial_json,再把完整 JSON 作为一段或多段发出。
|
||||||
|
// 真 Claude 会 chunk 成多段,我们没有中间态,但先发 "" 再发整块这个序列能通过结构校验。
|
||||||
|
writeSSE("content_block_delta", gin.H{
|
||||||
|
"type": "content_block_delta",
|
||||||
|
"index": blockIndex,
|
||||||
|
"delta": gin.H{"type": "input_json_delta", "partial_json": ""},
|
||||||
|
})
|
||||||
writeSSE("content_block_delta", gin.H{
|
writeSSE("content_block_delta", gin.H{
|
||||||
"type": "content_block_delta",
|
"type": "content_block_delta",
|
||||||
"index": blockIndex,
|
"index": blockIndex,
|
||||||
@ -457,16 +499,24 @@ func (s *WindsurfGatewayService) streamAnthropicResponse(c *gin.Context, id stri
|
|||||||
"index": 0,
|
"index": 0,
|
||||||
"content_block": gin.H{"type": "text", "text": ""},
|
"content_block": gin.H{"type": "text", "text": ""},
|
||||||
})
|
})
|
||||||
|
emitPingIfNeeded()
|
||||||
writeSSE("content_block_stop", gin.H{
|
writeSSE("content_block_stop", gin.H{
|
||||||
"type": "content_block_stop",
|
"type": "content_block_stop",
|
||||||
"index": 0,
|
"index": 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// message_delta: 真 Anthropic 的 usage 这里会带 output_tokens 累加值,
|
||||||
|
// 以及 cache_creation/read/input_tokens 镜像(签名检测对这里比较敏感)。
|
||||||
writeSSE("message_delta", gin.H{
|
writeSSE("message_delta", gin.H{
|
||||||
"type": "message_delta",
|
"type": "message_delta",
|
||||||
"delta": gin.H{"stop_reason": stopReason, "stop_sequence": nil},
|
"delta": gin.H{"stop_reason": stopReason, "stop_sequence": nil},
|
||||||
"usage": gin.H{"output_tokens": outputTokens},
|
"usage": gin.H{
|
||||||
|
"input_tokens": inputTokens,
|
||||||
|
"cache_creation_input_tokens": cacheWriteTokens,
|
||||||
|
"cache_read_input_tokens": cacheReadTokens,
|
||||||
|
"output_tokens": outputTokens,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
writeSSE("message_stop", gin.H{
|
writeSSE("message_stop", gin.H{
|
||||||
@ -686,3 +736,18 @@ func windsurfLogger(c *gin.Context, component string, fields ...zap.Field) *zap.
|
|||||||
}
|
}
|
||||||
return l.With(fields...)
|
return l.With(fields...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generateAnthropicMessageID 生成符合 Anthropic API 签名格式的消息 ID:
|
||||||
|
// "msg_01" 前缀 + 22 位 base62 随机字符(总长 28 字符,与官方 msg_013Zva2CMHLNnXjNJJKqJ2EF 一致)。
|
||||||
|
// 签名校验类工具常按 prefix/length 校验,长度差一位就会挂。
|
||||||
|
func generateAnthropicMessageID() string {
|
||||||
|
const alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||||
|
const suffixLen = 22
|
||||||
|
var buf [suffixLen]byte
|
||||||
|
_, _ = rand.Read(buf[:])
|
||||||
|
out := make([]byte, suffixLen)
|
||||||
|
for i, b := range buf {
|
||||||
|
out[i] = alphabet[int(b)%len(alphabet)]
|
||||||
|
}
|
||||||
|
return "msg_01" + string(out)
|
||||||
|
}
|
||||||
|
|||||||
@ -119,16 +119,16 @@ func NewWindsurfAuthService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
type WindsurfLoginInput struct {
|
type WindsurfLoginInput struct {
|
||||||
Email string
|
Email string
|
||||||
Password string
|
Password string
|
||||||
Name string
|
Name string
|
||||||
Notes *string
|
Notes *string
|
||||||
ProxyID *int64
|
ProxyID *int64
|
||||||
GroupIDs []int64
|
GroupIDs []int64
|
||||||
Concurrency int
|
Concurrency int
|
||||||
Priority int
|
Priority int
|
||||||
ProbeAfter bool
|
ProbeAfter bool
|
||||||
LSInstanceID string
|
LSInstanceID string
|
||||||
}
|
}
|
||||||
|
|
||||||
type WindsurfLoginOutput struct {
|
type WindsurfLoginOutput struct {
|
||||||
|
|||||||
@ -32,11 +32,11 @@ func NewWindsurfTokenProvider(
|
|||||||
}
|
}
|
||||||
|
|
||||||
type WindsurfToken struct {
|
type WindsurfToken struct {
|
||||||
APIKey string
|
APIKey string
|
||||||
ProxyURL string
|
ProxyURL string
|
||||||
AccountID int64
|
AccountID int64
|
||||||
Tier string
|
Tier string
|
||||||
LSBinding WindsurfLSBinding
|
LSBinding WindsurfLSBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *WindsurfTokenProvider) GetToken(ctx context.Context, accountID int64) (*WindsurfToken, error) {
|
func (p *WindsurfTokenProvider) GetToken(ctx context.Context, accountID int64) (*WindsurfToken, error) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user