feat(api): 统一官方兼容接口响应协议

兼容接口现在以入口协议作为最终响应协议,同协议保留官方 Wire 响应,跨协议统一转换成功、任务状态与错误结构。

同时修正异步提交状态边界,持久化兼容公开任务标识和官方提交响应,并新增迁移、流式响应及协议契约测试。

验证:go vet ./...;go test ./...;govulncheck ./...;pnpm lint;pnpm test;pnpm build;pnpm audit --audit-level high;pnpm openapi;全部 CI 脚本。
This commit is contained in:
2026-07-22 15:34:59 +08:00
parent 42e8b517fd
commit e07a997aa9
32 changed files with 2436 additions and 591 deletions
+77 -58
View File
@@ -486,64 +486,70 @@ type CreateTaskResult struct {
}
type GatewayTask struct {
ID string `json:"id"`
ExternalTaskID string `json:"externalTaskId,omitempty"`
Kind string `json:"kind"`
RunMode string `json:"runMode"`
UserID string `json:"userId"`
GatewayUserID string `json:"gatewayUserId,omitempty"`
UserSource string `json:"userSource,omitempty"`
GatewayTenantID string `json:"gatewayTenantId,omitempty"`
TenantID string `json:"tenantId,omitempty"`
TenantKey string `json:"tenantKey,omitempty"`
APIKeyID string `json:"apiKeyId,omitempty"`
APIKeyName string `json:"apiKeyName,omitempty"`
APIKeyPrefix string `json:"apiKeyPrefix,omitempty"`
UserGroupID string `json:"userGroupId,omitempty"`
UserGroupKey string `json:"userGroupKey,omitempty"`
Model string `json:"model"`
ModelType string `json:"modelType,omitempty"`
RequestedModel string `json:"requestedModel,omitempty"`
ResolvedModel string `json:"resolvedModel,omitempty"`
RequestID string `json:"requestId,omitempty"`
ConversationID string `json:"conversationId,omitempty"`
NewMessageCount int `json:"newMessageCount,omitempty"`
Request map[string]any `json:"request,omitempty"`
AsyncMode bool `json:"asyncMode"`
RiverJobID int64 `json:"riverJobId,omitempty"`
Status string `json:"status"`
Cancellable *bool `json:"cancellable,omitempty"`
Submitted *bool `json:"submitted,omitempty"`
Message string `json:"message,omitempty"`
AttemptCount int `json:"attemptCount"`
RemoteTaskID string `json:"remoteTaskId,omitempty"`
RemoteTaskPayload map[string]any `json:"-"`
Result map[string]any `json:"result,omitempty"`
Billings []any `json:"billings,omitempty"`
Usage map[string]any `json:"usage"`
Metrics map[string]any `json:"metrics"`
BillingSummary map[string]any `json:"billingSummary"`
FinalChargeAmount float64 `json:"finalChargeAmount"`
BillingVersion string `json:"billingVersion"`
BillingStatus string `json:"billingStatus"`
BillingCurrency string `json:"billingCurrency"`
PricingSnapshot map[string]any `json:"pricingSnapshot,omitempty"`
RequestFingerprint string `json:"requestFingerprint,omitempty"`
ReservationAmount float64 `json:"reservationAmount"`
ExecutionToken string `json:"-"`
ExecutionLeaseUntil string `json:"executionLeaseExpiresAt,omitempty"`
BillingUpdatedAt string `json:"billingUpdatedAt,omitempty"`
BillingSettledAt string `json:"billingSettledAt,omitempty"`
ResponseStartedAt string `json:"responseStartedAt,omitempty"`
ResponseFinishedAt string `json:"responseFinishedAt,omitempty"`
ResponseDurationMS int64 `json:"responseDurationMs"`
FinishedAt string `json:"finishedAt,omitempty"`
Error string `json:"error,omitempty"`
ErrorCode string `json:"errorCode,omitempty"`
ErrorMessage string `json:"errorMessage,omitempty"`
Attempts []TaskAttempt `json:"attempts,omitempty"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
ID string `json:"id"`
ExternalTaskID string `json:"externalTaskId,omitempty"`
Kind string `json:"kind"`
RunMode string `json:"runMode"`
UserID string `json:"userId"`
GatewayUserID string `json:"gatewayUserId,omitempty"`
UserSource string `json:"userSource,omitempty"`
GatewayTenantID string `json:"gatewayTenantId,omitempty"`
TenantID string `json:"tenantId,omitempty"`
TenantKey string `json:"tenantKey,omitempty"`
APIKeyID string `json:"apiKeyId,omitempty"`
APIKeyName string `json:"apiKeyName,omitempty"`
APIKeyPrefix string `json:"apiKeyPrefix,omitempty"`
UserGroupID string `json:"userGroupId,omitempty"`
UserGroupKey string `json:"userGroupKey,omitempty"`
Model string `json:"model"`
ModelType string `json:"modelType,omitempty"`
RequestedModel string `json:"requestedModel,omitempty"`
ResolvedModel string `json:"resolvedModel,omitempty"`
RequestID string `json:"requestId,omitempty"`
ConversationID string `json:"conversationId,omitempty"`
NewMessageCount int `json:"newMessageCount,omitempty"`
Request map[string]any `json:"request,omitempty"`
AsyncMode bool `json:"asyncMode"`
RiverJobID int64 `json:"riverJobId,omitempty"`
Status string `json:"status"`
Cancellable *bool `json:"cancellable,omitempty"`
Submitted *bool `json:"submitted,omitempty"`
Message string `json:"message,omitempty"`
AttemptCount int `json:"attemptCount"`
RemoteTaskID string `json:"remoteTaskId,omitempty"`
RemoteTaskPayload map[string]any `json:"-"`
Result map[string]any `json:"result,omitempty"`
Billings []any `json:"billings,omitempty"`
Usage map[string]any `json:"usage"`
Metrics map[string]any `json:"metrics"`
BillingSummary map[string]any `json:"billingSummary"`
FinalChargeAmount float64 `json:"finalChargeAmount"`
BillingVersion string `json:"billingVersion"`
BillingStatus string `json:"billingStatus"`
BillingCurrency string `json:"billingCurrency"`
PricingSnapshot map[string]any `json:"pricingSnapshot,omitempty"`
RequestFingerprint string `json:"requestFingerprint,omitempty"`
ReservationAmount float64 `json:"reservationAmount"`
ExecutionToken string `json:"-"`
ExecutionLeaseUntil string `json:"executionLeaseExpiresAt,omitempty"`
BillingUpdatedAt string `json:"billingUpdatedAt,omitempty"`
BillingSettledAt string `json:"billingSettledAt,omitempty"`
ResponseStartedAt string `json:"responseStartedAt,omitempty"`
ResponseFinishedAt string `json:"responseFinishedAt,omitempty"`
ResponseDurationMS int64 `json:"responseDurationMs"`
FinishedAt string `json:"finishedAt,omitempty"`
Error string `json:"error,omitempty"`
ErrorCode string `json:"errorCode,omitempty"`
ErrorMessage string `json:"errorMessage,omitempty"`
CompatibilityProtocol string `json:"compatibilityProtocol,omitempty"`
CompatibilityPublicID string `json:"compatibilityPublicId,omitempty"`
CompatibilitySourceProtocol string `json:"compatibilitySourceProtocol,omitempty"`
CompatibilitySubmitHTTPStatus int `json:"compatibilitySubmitHttpStatus,omitempty"`
CompatibilitySubmitHeaders map[string]any `json:"compatibilitySubmitHeaders,omitempty"`
CompatibilitySubmitBody map[string]any `json:"compatibilitySubmitBody,omitempty"`
Attempts []TaskAttempt `json:"attempts,omitempty"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
const gatewayTaskColumns = `
@@ -564,6 +570,9 @@ COALESCE(execution_lease_expires_at::text, ''), COALESCE(billing_updated_at::tex
COALESCE(billing_settled_at::text, ''), COALESCE(response_started_at::text, ''),
COALESCE(response_finished_at::text, ''), COALESCE(response_duration_ms, 0), COALESCE(error, ''),
COALESCE(error_code, ''), COALESCE(error_message, ''),
COALESCE(compatibility_protocol, ''), COALESCE(compatibility_public_id, ''),
COALESCE(compatibility_source_protocol, ''), COALESCE(compatibility_submit_http_status, 0),
COALESCE(compatibility_submit_headers, '{}'::jsonb), COALESCE(compatibility_submit_body, '{}'::jsonb),
created_at, updated_at, COALESCE(finished_at::text, '')`
type TaskEvent struct {
@@ -2055,6 +2064,8 @@ func scanGatewayTask(scanner taskScanner) (GatewayTask, error) {
var billingSummaryBytes []byte
var pricingSnapshotBytes []byte
var remoteTaskPayloadBytes []byte
var compatibilitySubmitHeadersBytes []byte
var compatibilitySubmitBodyBytes []byte
if err := scanner.Scan(
&task.ID,
&task.ExternalTaskID,
@@ -2107,6 +2118,12 @@ func scanGatewayTask(scanner taskScanner) (GatewayTask, error) {
&task.Error,
&task.ErrorCode,
&task.ErrorMessage,
&task.CompatibilityProtocol,
&task.CompatibilityPublicID,
&task.CompatibilitySourceProtocol,
&task.CompatibilitySubmitHTTPStatus,
&compatibilitySubmitHeadersBytes,
&compatibilitySubmitBodyBytes,
&task.CreatedAt,
&task.UpdatedAt,
&task.FinishedAt,
@@ -2121,6 +2138,8 @@ func scanGatewayTask(scanner taskScanner) (GatewayTask, error) {
task.Metrics = decodeObject(metricsBytes)
task.BillingSummary = decodeObject(billingSummaryBytes)
task.PricingSnapshot = decodeObject(pricingSnapshotBytes)
task.CompatibilitySubmitHeaders = decodeObject(compatibilitySubmitHeadersBytes)
task.CompatibilitySubmitBody = decodeObject(compatibilitySubmitBodyBytes)
return task, nil
}