intelligent_test_runner.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. package service
  2. import (
  3. "bufio"
  4. "bytes"
  5. "context"
  6. "encoding/json"
  7. "errors"
  8. "io"
  9. "net/http"
  10. "strings"
  11. "sync"
  12. "time"
  13. "github.com/Wei-Shaw/sub2api/internal/pkg/antigravity"
  14. "github.com/Wei-Shaw/sub2api/internal/pkg/tlsfingerprint"
  15. "github.com/Wei-Shaw/sub2api/internal/util/logredact"
  16. "github.com/gin-gonic/gin"
  17. )
  18. type intelligentRunKey struct{}
  19. type intelligentRunContext struct {
  20. prompt string
  21. capture *intelligentCapture
  22. }
  23. // Only an empty selection receives the protocol default. Explicit choices
  24. // remain observable even when the upstream rejects the requested model.
  25. func resolveIntelligentTestModel(account *Account, configured string) string {
  26. model := strings.TrimSpace(configured)
  27. if account != nil && account.UsesOpenAICodexProtocol() && model == "" {
  28. return "gpt-5.3-codex"
  29. }
  30. return model
  31. }
  32. func intelligentContext(ctx context.Context) *intelligentRunContext {
  33. v, _ := ctx.Value(intelligentRunKey{}).(*intelligentRunContext)
  34. return v
  35. }
  36. func intelligentPrompt(ctx context.Context) string {
  37. if v := intelligentContext(ctx); v != nil {
  38. return v.prompt
  39. }
  40. return ""
  41. }
  42. func applyIntelligentPayloadPrompt(ctx context.Context, payload map[string]any) {
  43. prompt := intelligentPrompt(ctx)
  44. if prompt == "" {
  45. return
  46. }
  47. if _, ok := payload["input"]; ok {
  48. payload["input"] = []map[string]any{{"role": "user", "content": []map[string]any{{"type": "input_text", "text": prompt}}}}
  49. }
  50. if _, ok := payload["messages"]; ok {
  51. payload["messages"] = []map[string]any{{"role": "user", "content": []map[string]any{{"type": "text", "text": prompt}}}}
  52. if _, bounded := payload["max_tokens"]; bounded {
  53. payload["max_tokens"] = 8192
  54. }
  55. }
  56. }
  57. func intelligentAntigravityBody(ctx context.Context, body []byte) ([]byte, error) {
  58. var envelope map[string]any
  59. if err := json.Unmarshal(body, &envelope); err != nil {
  60. return nil, err
  61. }
  62. request, ok := envelope["request"].(map[string]any)
  63. if !ok {
  64. return nil, errors.New("unsupported Antigravity request envelope")
  65. }
  66. request["contents"] = []map[string]any{{"role": "user", "parts": []map[string]any{{"text": intelligentPrompt(ctx)}}}}
  67. cfg, ok := request["generationConfig"].(map[string]any)
  68. if !ok {
  69. cfg = map[string]any{}
  70. request["generationConfig"] = cfg
  71. }
  72. cfg["maxOutputTokens"] = 8192
  73. return json.Marshal(envelope)
  74. }
  75. func (s *AntigravityGatewayService) runIntelligentAntigravity(ctx context.Context, account *Account, token string, body []byte) (*http.Response, error) {
  76. base := resolveAntigravityForwardBaseURL(account)
  77. if base == "" {
  78. return nil, errors.New("no antigravity forward base url configured")
  79. }
  80. req, err := antigravity.NewAPIRequestWithURL(ctx, base, "streamGenerateContent", token, body)
  81. if err != nil {
  82. return nil, err
  83. }
  84. proxy := ""
  85. if account.Proxy != nil && account.ProxyID != nil {
  86. proxy = account.Proxy.URL()
  87. }
  88. resp, err := s.httpUpstream.Do(WithAccountTrafficRequest(req, account), proxy, account.ID, account.Concurrency)
  89. return captureIntelligentResponse(req, resp), err
  90. }
  91. // RunIntelligentTest uses the existing authenticated outbound protocol adapters.
  92. // A per-run service avoids mutating shared service state. Runtime health writes
  93. // are suppressed: an observation never changes account policy or scheduling.
  94. func (s *AccountTestService) RunIntelligentTest(ctx context.Context, r *IntelligentTestRecord) error {
  95. account, err := s.accountRepo.GetByID(ctx, r.AccountID)
  96. if err != nil {
  97. r.Status = "account_error"
  98. return errors.New("account unavailable")
  99. }
  100. r.AntiDegradation = account.AntiDegradationEnabled()
  101. if r.ConfigSnapshot == nil {
  102. return errors.New("missing configuration")
  103. }
  104. r.Model = resolveIntelligentTestModel(account, r.ConfigSnapshot.Model)
  105. if err := accountTestCooldown(ctx, account, r.Model, time.Now()); err != nil {
  106. return err
  107. }
  108. release, err := s.acquireTestAccountSlot(ctx, account)
  109. if err != nil {
  110. return err
  111. }
  112. defer release()
  113. if r.ConfigSnapshot == nil {
  114. return errors.New("missing configuration")
  115. }
  116. // Keep the requested model and the resolved execution model separately.
  117. r.Model = resolveIntelligentTestModel(account, r.ConfigSnapshot.Model)
  118. if err := validateIntelligentTextModel(account.GetMappedModel(r.Model)); err != nil {
  119. r.Status = "request_error"
  120. return err
  121. }
  122. r.ConfigSnapshot.Execution = ResolveProtectionRuntime(account, s.cfg, s.pluginManager)
  123. r.ConfigSnapshot.Execution.RequestedModel = r.ConfigSnapshot.Model
  124. r.ConfigSnapshot.Execution.PromptDigest = protectionPromptDigest(r.Input)
  125. r.ConfigSnapshot.Execution.Model = r.Model
  126. capture := &intelligentCapture{}
  127. capture.collectCredentialSecrets(account.Credentials)
  128. ctx = context.WithValue(ctx, intelligentRunKey{}, &intelligentRunContext{prompt: r.Input, capture: capture})
  129. runCtx, cancel := context.WithCancel(ctx)
  130. defer cancel()
  131. recorder := &intelligentSSEWriter{header: http.Header{}, cancel: cancel}
  132. c, _ := gin.CreateTestContext(recorder)
  133. c.Request = (&http.Request{Method: http.MethodPost, Header: http.Header{}}).WithContext(runCtx)
  134. readRepo := &intelligentReadOnlyAccountRepo{AccountRepository: s.accountRepo, capture: capture, snapshot: account}
  135. clone := NewAccountTestService(readRepo, s.geminiTokenProvider, s.claudeTokenProvider, s.grokTokenProvider, s.antigravityGatewayService, &intelligentHTTPUpstream{inner: s.httpUpstream, capture: capture}, s.cfg, s.tlsFPProfileService)
  136. clone.settingService = s.settingService
  137. clone.pluginManager = s.pluginManager
  138. clone.openaiGatewayService = s.openaiGatewayService
  139. clone.agentIdentityWS = s.agentIdentityWS
  140. clone.grokWSDialer = s.grokWSDialer
  141. prepared, err := readRepo.GetByID(ctx, r.AccountID)
  142. if err != nil {
  143. r.Status = "account_error"
  144. return errors.New("account unavailable")
  145. }
  146. // Adaptive connection diagnostics probe several protocols and concatenate
  147. // answers. Capability tests use its native chat route once for a fair result.
  148. if prepared.IsCNProvider() && prepared.GetAPIProtocol() == APIProtocolAdaptive {
  149. err = clone.testCNProviderChatCompletionsConnection(c, prepared, r.Model, r.Input)
  150. } else {
  151. err = clone.testAccountConnectionForAccount(c, prepared, r.Model, r.Input, AccountTestModeDefault, AccountTestOptions{})
  152. }
  153. textOutput, eventError, model, complete := parseIntelligentSSE(recorder.body.String())
  154. if capture.trafficWait != nil && capture.status == 0 {
  155. return &TestAdmissionWaitError{Until: time.Now().Add(capture.trafficWait.RetryAfter), Reason: capture.trafficWait.Reason}
  156. }
  157. if capture.body.Len() > 0 && !intelligentRawComplete(capture.body.String()) {
  158. complete = false
  159. }
  160. r.Result = capture.redact(textOutput)
  161. if model != "" {
  162. r.Model = model
  163. }
  164. r.ConfigSnapshot.Execution.Model = r.Model
  165. r.RawResponse = capture.redact(capture.body.String())
  166. r.RawTruncated = capture.truncated || recorder.truncated
  167. if r.RawResponse == "" {
  168. r.RawResponse = capture.redact(recorder.body.String())
  169. }
  170. r.ErrorMessage = capture.redact(eventError)
  171. if r.ErrorMessage == "" && err != nil {
  172. r.ErrorMessage = capture.redact(err.Error())
  173. }
  174. if len(r.Result) > 512<<10 {
  175. r.Result = r.Result[:512<<10]
  176. r.RawTruncated = true
  177. err = errors.New("test output exceeded capture limit")
  178. }
  179. if len(r.ErrorMessage) > 4096 {
  180. r.ErrorMessage = r.ErrorMessage[:4096]
  181. }
  182. r.Result = strings.ToValidUTF8(r.Result, "�")
  183. r.RawResponse = strings.ToValidUTF8(r.RawResponse, "�")
  184. r.ErrorMessage = strings.ToValidUTF8(r.ErrorMessage, "�")
  185. if err != nil || eventError != "" || !complete || strings.TrimSpace(r.Result) == "" || recorder.truncated || capture.truncated {
  186. if err == nil {
  187. err = errors.New("upstream did not return a complete, nonempty test result")
  188. }
  189. if r.ErrorMessage == "" {
  190. r.ErrorMessage = err.Error()
  191. }
  192. r.Status = classifyIntelligentError(capture.status, r.ErrorMessage)
  193. return err
  194. }
  195. return nil
  196. }
  197. // Some old connectivity adapters treat EOF as success. Capability scoring
  198. // requires a real terminal event and rejects token-budget truncation.
  199. func intelligentRawComplete(raw string) bool {
  200. complete, truncated := false, false
  201. var inspect func(map[string]any)
  202. inspect = func(v map[string]any) {
  203. if nested, ok := v["response"].(map[string]any); ok {
  204. inspect(nested)
  205. }
  206. kind, _ := v["type"].(string)
  207. if kind == "response.completed" || kind == "response.done" || kind == "message_stop" {
  208. complete = true
  209. }
  210. if status, _ := v["status"].(string); status == "incomplete" || status == "failed" {
  211. truncated = true
  212. }
  213. if reason, _ := v["stop_reason"].(string); reason != "" {
  214. if reason == "max_tokens" {
  215. truncated = true
  216. } else {
  217. complete = true
  218. }
  219. }
  220. if delta, ok := v["delta"].(map[string]any); ok {
  221. inspect(delta)
  222. }
  223. if choices, ok := v["choices"].([]any); ok {
  224. for _, item := range choices {
  225. if choice, ok := item.(map[string]any); ok {
  226. if reason, _ := choice["finish_reason"].(string); reason != "" {
  227. if reason == "length" || reason == "content_filter" {
  228. truncated = true
  229. } else {
  230. complete = true
  231. }
  232. }
  233. }
  234. }
  235. }
  236. if candidates, ok := v["candidates"].([]any); ok {
  237. for _, item := range candidates {
  238. if candidate, ok := item.(map[string]any); ok {
  239. if reason, _ := candidate["finishReason"].(string); reason != "" {
  240. if reason == "STOP" {
  241. complete = true
  242. } else {
  243. truncated = true
  244. }
  245. }
  246. }
  247. }
  248. }
  249. }
  250. var body map[string]any
  251. if json.Unmarshal([]byte(raw), &body) == nil {
  252. inspect(body)
  253. } else {
  254. scanner := bufio.NewScanner(strings.NewReader(raw))
  255. scanner.Buffer(make([]byte, 8192), 4<<20)
  256. for scanner.Scan() {
  257. line := strings.TrimSpace(scanner.Text())
  258. if !strings.HasPrefix(line, "data:") {
  259. continue
  260. }
  261. var event map[string]any
  262. if json.Unmarshal([]byte(strings.TrimSpace(strings.TrimPrefix(line, "data:"))), &event) == nil {
  263. inspect(event)
  264. }
  265. }
  266. }
  267. return complete && !truncated
  268. }
  269. func parseIntelligentSSE(raw string) (output, errorMessage, model string, complete bool) {
  270. var text strings.Builder
  271. scanner := bufio.NewScanner(strings.NewReader(raw))
  272. scanner.Buffer(make([]byte, 8192), 1<<20)
  273. for scanner.Scan() {
  274. line := scanner.Text()
  275. if !strings.HasPrefix(line, "data:") {
  276. continue
  277. }
  278. var e TestEvent
  279. if json.Unmarshal([]byte(strings.TrimSpace(strings.TrimPrefix(line, "data:"))), &e) != nil {
  280. continue
  281. }
  282. switch e.Type {
  283. case "content":
  284. text.WriteString(e.Text)
  285. case "error":
  286. errorMessage = e.Error
  287. case "test_start":
  288. model = e.Model
  289. case "test_complete":
  290. complete = e.Success
  291. }
  292. }
  293. return text.String(), errorMessage, model, complete
  294. }
  295. func classifyIntelligentError(code int, message string) string {
  296. lower := strings.ToLower(message)
  297. switch {
  298. case code == 429 || strings.Contains(lower, "rate limit") || strings.Contains(lower, "限流"):
  299. return "rate_limited"
  300. case code == 401 || code == 403:
  301. return "account_error"
  302. case code >= 500:
  303. return "network_error"
  304. case strings.Contains(lower, "max_tokens") || strings.Contains(lower, "max_output_tokens") || strings.Contains(lower, "max_completion_tokens"):
  305. return "request_error"
  306. case code == 404 || strings.Contains(lower, "model not found") || strings.Contains(lower, "unsupported model"):
  307. return "model_error"
  308. case code == 400 || code == 422:
  309. return "request_error"
  310. case strings.Contains(lower, "expired token") || strings.Contains(lower, "access token") || strings.Contains(lower, "credential") || strings.Contains(lower, "api key") || strings.Contains(lower, "account not found") || strings.Contains(message, "账号认证"):
  311. return "account_error"
  312. case strings.Contains(lower, "timeout") || strings.Contains(lower, "connection") || strings.Contains(lower, "deadline"):
  313. return "network_error"
  314. default:
  315. return "failed"
  316. }
  317. }
  318. func validateIntelligentTextModel(model string) error {
  319. lower := strings.ToLower(strings.TrimSpace(model))
  320. if isOpenAIImageModel(model) || strings.HasPrefix(lower, "grok-imagine") || strings.HasPrefix(lower, "tts-") || strings.HasPrefix(lower, "whisper") || strings.HasPrefix(lower, "sora") {
  321. return errors.New("当前题目需要文本生成模型;鹈鹕测试通过 SVG 源码绘图,请勿选择原生图片、音频或视频模型")
  322. }
  323. return nil
  324. }
  325. type intelligentSSEWriter struct {
  326. header http.Header
  327. body bytes.Buffer
  328. cancel context.CancelFunc
  329. truncated bool
  330. }
  331. func (w *intelligentSSEWriter) Header() http.Header { return w.header }
  332. func (w *intelligentSSEWriter) WriteHeader(int) {}
  333. func (w *intelligentSSEWriter) Flush() {}
  334. func (w *intelligentSSEWriter) Write(p []byte) (int, error) {
  335. if w.body.Len()+len(p) > 2<<20 {
  336. w.truncated = true
  337. w.cancel()
  338. return 0, errors.New("test output too large")
  339. }
  340. return w.body.Write(p)
  341. }
  342. type intelligentCapture struct {
  343. trafficWait *AccountTrafficLimitError
  344. mu sync.Mutex
  345. body bytes.Buffer
  346. status int
  347. truncated bool
  348. secrets []string
  349. }
  350. func (c *intelligentCapture) Write(p []byte) (int, error) {
  351. c.mu.Lock()
  352. defer c.mu.Unlock()
  353. n := len(p)
  354. available := (1 << 20) - c.body.Len()
  355. if available < len(p) {
  356. c.truncated = true
  357. if available > 0 {
  358. _, _ = c.body.Write(p[:available])
  359. }
  360. } else {
  361. _, _ = c.body.Write(p)
  362. }
  363. return n, nil
  364. }
  365. func (c *intelligentCapture) collectCredentialSecrets(values map[string]any) {
  366. for key, value := range values {
  367. if nested, ok := value.(map[string]any); ok {
  368. c.collectCredentialSecrets(nested)
  369. }
  370. lower := strings.ToLower(key)
  371. if strings.Contains(lower, "token") || strings.Contains(lower, "secret") || strings.Contains(lower, "password") || strings.Contains(lower, "key") || strings.Contains(lower, "cookie") {
  372. if text, ok := value.(string); ok && len(text) >= 6 {
  373. c.secrets = append(c.secrets, text)
  374. }
  375. }
  376. }
  377. }
  378. func (c *intelligentCapture) redact(raw string) string {
  379. for _, secret := range c.secrets {
  380. raw = strings.ReplaceAll(raw, secret, "[REDACTED]")
  381. }
  382. return logredact.RedactText(raw)
  383. }
  384. type intelligentCaptureBody struct {
  385. io.Reader
  386. closer io.Closer
  387. }
  388. func (b *intelligentCaptureBody) Close() error { return b.closer.Close() }
  389. func (c *intelligentCapture) response(req *http.Request, resp *http.Response) *http.Response {
  390. if resp == nil {
  391. return resp
  392. }
  393. c.status = resp.StatusCode
  394. for _, key := range []string{"Authorization", "X-Api-Key", "X-Goog-Api-Key", "Cookie"} {
  395. v := req.Header.Get(key)
  396. if len(v) > 6 {
  397. c.secrets = append(c.secrets, v)
  398. if strings.HasPrefix(v, "Bearer ") {
  399. c.secrets = append(c.secrets, strings.TrimPrefix(v, "Bearer "))
  400. }
  401. }
  402. }
  403. if resp.Body != nil {
  404. if _, ok := resp.Body.(*intelligentCaptureBody); !ok {
  405. resp.Body = &intelligentCaptureBody{Reader: io.TeeReader(io.LimitReader(resp.Body, 4<<20), c), closer: resp.Body}
  406. }
  407. }
  408. return resp
  409. }
  410. type intelligentHTTPUpstream struct {
  411. inner HTTPUpstream
  412. capture *intelligentCapture
  413. }
  414. func (h *intelligentHTTPUpstream) Do(req *http.Request, proxy string, id int64, concurrency int) (*http.Response, error) {
  415. resp, err := h.inner.Do(req, proxy, id, concurrency)
  416. var wait *AccountTrafficLimitError
  417. if errors.As(err, &wait) {
  418. h.capture.trafficWait = wait
  419. }
  420. return h.capture.response(req, resp), err
  421. }
  422. func (h *intelligentHTTPUpstream) DoWithTLS(req *http.Request, proxy string, id int64, concurrency int, p *tlsfingerprint.Profile) (*http.Response, error) {
  423. resp, err := h.inner.DoWithTLS(req, proxy, id, concurrency, p)
  424. var wait *AccountTrafficLimitError
  425. if errors.As(err, &wait) {
  426. h.capture.trafficWait = wait
  427. }
  428. return h.capture.response(req, resp), err
  429. }
  430. func (h *intelligentHTTPUpstream) AccountTrafficController() *AccountTrafficService {
  431. return accountTrafficController(h.inner)
  432. }
  433. func captureIntelligentResponse(req *http.Request, resp *http.Response) *http.Response {
  434. if v := intelligentContext(req.Context()); v != nil {
  435. return v.capture.response(req, resp)
  436. }
  437. return resp
  438. }
  439. type intelligentReadOnlyAccountRepo struct {
  440. AccountRepository
  441. capture *intelligentCapture
  442. snapshot *Account
  443. }
  444. func (r *intelligentReadOnlyAccountRepo) GetByID(ctx context.Context, id int64) (*Account, error) {
  445. a := r.snapshot
  446. if a == nil || a.ID != id {
  447. var err error
  448. a, err = r.AccountRepository.GetByID(ctx, id)
  449. if err != nil {
  450. return nil, err
  451. }
  452. }
  453. copy := *a
  454. raw, _ := json.Marshal(a.Extra)
  455. copy.Extra = nil
  456. _ = json.Unmarshal(raw, &copy.Extra)
  457. raw, _ = json.Marshal(a.Credentials)
  458. copy.Credentials = nil
  459. _ = json.Unmarshal(raw, &copy.Credentials)
  460. r.capture.collectCredentialSecrets(copy.Credentials)
  461. return &copy, nil
  462. }
  463. func (r *intelligentReadOnlyAccountRepo) SetError(context.Context, int64, string) error { return nil }
  464. func (r *intelligentReadOnlyAccountRepo) ClearError(context.Context, int64) error { return nil }
  465. func (r *intelligentReadOnlyAccountRepo) SetRateLimited(context.Context, int64, time.Time) error {
  466. return nil
  467. }
  468. func (r *intelligentReadOnlyAccountRepo) ClearRateLimit(context.Context, int64) error { return nil }
  469. func (r *intelligentReadOnlyAccountRepo) SetModelRateLimit(context.Context, int64, string, time.Time, ...string) error {
  470. return nil
  471. }
  472. func (r *intelligentReadOnlyAccountRepo) SetTempUnschedulable(context.Context, int64, time.Time, string) error {
  473. return nil
  474. }
  475. func (r *intelligentReadOnlyAccountRepo) SetOverloaded(context.Context, int64, time.Time) error {
  476. return nil
  477. }
  478. func (r *intelligentReadOnlyAccountRepo) SetSchedulable(context.Context, int64, bool) error {
  479. return nil
  480. }
  481. func (r *intelligentReadOnlyAccountRepo) UpdateExtra(context.Context, int64, map[string]any) error {
  482. return nil
  483. }
  484. func (r *intelligentReadOnlyAccountRepo) Update(context.Context, *Account) error {
  485. return errors.New("capability tests do not modify account settings; refresh account authentication separately")
  486. }
  487. func (r *intelligentReadOnlyAccountRepo) Create(context.Context, *Account) error {
  488. return errors.New("capability tests cannot create accounts")
  489. }
  490. func (r *intelligentReadOnlyAccountRepo) Delete(context.Context, int64) error {
  491. return errors.New("capability tests cannot delete accounts")
  492. }
  493. func (r *intelligentReadOnlyAccountRepo) UpdateLastUsed(context.Context, int64) error { return nil }
  494. func (r *intelligentReadOnlyAccountRepo) BatchUpdateLastUsed(context.Context, map[int64]time.Time) error {
  495. return nil
  496. }
  497. func (r *intelligentReadOnlyAccountRepo) AutoPauseExpiredAccounts(context.Context, time.Time) (int64, error) {
  498. return 0, nil
  499. }
  500. func (r *intelligentReadOnlyAccountRepo) BindGroups(context.Context, int64, []int64) error {
  501. return errors.New("capability tests cannot change account groups")
  502. }
  503. func (r *intelligentReadOnlyAccountRepo) ClearTempUnschedulable(context.Context, int64) error {
  504. return nil
  505. }
  506. func (r *intelligentReadOnlyAccountRepo) ClearAntigravityQuotaScopes(context.Context, int64) error {
  507. return nil
  508. }
  509. func (r *intelligentReadOnlyAccountRepo) ClearModelRateLimits(context.Context, int64) error {
  510. return nil
  511. }
  512. func (r *intelligentReadOnlyAccountRepo) UpdateSessionWindow(context.Context, int64, *time.Time, *time.Time, string) error {
  513. return nil
  514. }
  515. func (r *intelligentReadOnlyAccountRepo) UpdateSessionWindowEnd(context.Context, int64, time.Time) error {
  516. return nil
  517. }
  518. func (r *intelligentReadOnlyAccountRepo) BulkUpdate(context.Context, []int64, AccountBulkUpdate) (int64, error) {
  519. return 0, errors.New("capability tests cannot bulk edit account settings")
  520. }
  521. func (r *intelligentReadOnlyAccountRepo) IncrementQuotaUsed(context.Context, int64, float64) error {
  522. return nil
  523. }
  524. func (r *intelligentReadOnlyAccountRepo) ResetQuotaUsedAndClearRateLimitCooldown(context.Context, int64) error {
  525. return nil
  526. }
  527. func (r *intelligentReadOnlyAccountRepo) RevertProxyFallback(context.Context, int64) error {
  528. return errors.New("capability tests cannot change account proxy settings")
  529. }
  530. var _ HTTPUpstream = (*intelligentHTTPUpstream)(nil)