0.11.2: 旗舰版第三轮完善(通用聊天/企微钉钉飞书扫码登录/个人安全策略)
- 门户通用聊天:选择已批准模型直接对话,审批通过后自动开通用户级运行时 API Key(加密落库,限额取批准值),聊天经受管网关统一认证/限流/配额/审计; 会话哈希链完整性 + busy 租约防并发,失败不落库。 - 扫码登录:identity_providers 扩展 wecom/dingtalk/feishu,管理端配置 (AppID/AppSecret/AgentID/回调/自动开户/默认部门),登录页自动展示; one-time state 防 CSRF,provider_uid 全局唯一防多账号绑定,平台端点 固定公网 URL 复用 public-only 拨号。 - 个人安全策略:账号安全页(登录设备管理/吊销非当前会话/登录提醒开关/ 扫码绑定解绑),登录成功发布 security.login_detected 事件按偏好落站内信 (新增 security 类别),会话索引只存令牌摘要并惰性清理。 - 迁移 000038-000041;修复 social update 参数越界/凭据回读/路由挂载缺失; 全量测试 25 包通过,前端 admin/portal 构建通过,端到端验证完成。
This commit is contained in:
@@ -14,10 +14,10 @@ type outputRedactReadCloser struct {
|
||||
engine interface {
|
||||
OutputRedact([]byte) ([]byte, bool)
|
||||
}
|
||||
sse bool
|
||||
sse bool
|
||||
buffered []byte // 已处理待输出的字节
|
||||
done bool // 非流式已完成缓冲与处理
|
||||
pending []byte // 流式:未完成的行
|
||||
done bool // 非流式已完成缓冲与处理
|
||||
pending []byte // 流式:未完成的行
|
||||
}
|
||||
|
||||
func newOutputRedactReadCloser(body io.ReadCloser, contentType string, engine interface {
|
||||
|
||||
+15
-15
@@ -21,20 +21,20 @@ import (
|
||||
)
|
||||
|
||||
type Proxy struct {
|
||||
resolver AdapterResolver
|
||||
auth apikey.KeyAuthenticator
|
||||
maxBody int64
|
||||
logger *slog.Logger
|
||||
transport *http.Transport
|
||||
proxies sync.Map
|
||||
circuits sync.Map
|
||||
admission AdmissionController
|
||||
tokenQuota TokenQuotaController
|
||||
modelQuota ModelQuotaController
|
||||
resilience ResiliencePolicy
|
||||
audit AuditRecorder
|
||||
policies *contentpolicy.Engine
|
||||
pricing *pricing.Service
|
||||
resolver AdapterResolver
|
||||
auth apikey.KeyAuthenticator
|
||||
maxBody int64
|
||||
logger *slog.Logger
|
||||
transport *http.Transport
|
||||
proxies sync.Map
|
||||
circuits sync.Map
|
||||
admission AdmissionController
|
||||
tokenQuota TokenQuotaController
|
||||
modelQuota ModelQuotaController
|
||||
resilience ResiliencePolicy
|
||||
audit AuditRecorder
|
||||
policies *contentpolicy.Engine
|
||||
pricing *pricing.Service
|
||||
outputPolicies interface {
|
||||
OutputRedact([]byte) ([]byte, bool)
|
||||
}
|
||||
@@ -122,7 +122,7 @@ func (p *Proxy) SetOutputPolicyEngine(engine interface {
|
||||
}) {
|
||||
p.outputPolicies = engine
|
||||
}
|
||||
func (p *Proxy) SetPricingService(service *pricing.Service) { p.pricing = service }
|
||||
func (p *Proxy) SetPricingService(service *pricing.Service) { p.pricing = service }
|
||||
|
||||
func (p *Proxy) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
started := time.Now()
|
||||
|
||||
+14
-14
@@ -123,19 +123,19 @@ type usageCollector struct {
|
||||
}
|
||||
|
||||
func (c *usageCollector) feed(chunk []byte) {
|
||||
if !c.sse {
|
||||
c.doc = append(c.doc, chunk...)
|
||||
// Keep only a bounded tail window. The "usage" member lives at the end
|
||||
// of a non-streaming response, so dropping the head (never the tail)
|
||||
// preserves accounting for arbitrarily large bodies at a fixed memory
|
||||
// cost instead of truncating usage away. 仅在超过 2× 窗口时压缩一次,
|
||||
// 避免每个 32KiB 块都做 O(窗口) 的尾部拷贝(大响应下退化为 O(n²))。
|
||||
if len(c.doc) > 2*maxUsageDocumentBytes {
|
||||
copy(c.doc, c.doc[len(c.doc)-maxUsageDocumentBytes:])
|
||||
c.doc = c.doc[:maxUsageDocumentBytes]
|
||||
}
|
||||
return
|
||||
if !c.sse {
|
||||
c.doc = append(c.doc, chunk...)
|
||||
// Keep only a bounded tail window. The "usage" member lives at the end
|
||||
// of a non-streaming response, so dropping the head (never the tail)
|
||||
// preserves accounting for arbitrarily large bodies at a fixed memory
|
||||
// cost instead of truncating usage away. 仅在超过 2× 窗口时压缩一次,
|
||||
// 避免每个 32KiB 块都做 O(窗口) 的尾部拷贝(大响应下退化为 O(n²))。
|
||||
if len(c.doc) > 2*maxUsageDocumentBytes {
|
||||
copy(c.doc, c.doc[len(c.doc)-maxUsageDocumentBytes:])
|
||||
c.doc = c.doc[:maxUsageDocumentBytes]
|
||||
}
|
||||
return
|
||||
}
|
||||
c.pending = append(c.pending, chunk...)
|
||||
for {
|
||||
index := bytes.IndexByte(c.pending, '\n')
|
||||
@@ -169,8 +169,8 @@ func (c *usageCollector) usage() TokenUsage {
|
||||
c.pending = nil
|
||||
}
|
||||
if !c.sse && len(c.doc) > 0 {
|
||||
c.consumeJSON(c.doc) // fast path: whole valid JSON object
|
||||
c.consumeUsageObject(c.doc) // tail extraction: covers truncated bodies
|
||||
c.consumeJSON(c.doc) // fast path: whole valid JSON object
|
||||
c.consumeUsageObject(c.doc) // tail extraction: covers truncated bodies
|
||||
}
|
||||
return TokenUsage{Input: c.input, Output: c.output, Total: max(c.total, c.input+c.output)}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user