Skip to content

Commit e963714

Browse files
committed
fix(openai): pass service_tier by default
1 parent 1879038 commit e963714

6 files changed

Lines changed: 102 additions & 93 deletions

File tree

backend/internal/server/api_contract_test.go

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -784,14 +784,7 @@ func TestAPIContracts(t *testing.T) {
784784
"payment_visible_method_wxpay_enabled": false,
785785
"openai_advanced_scheduler_enabled": true,
786786
"openai_fast_policy_settings": {
787-
"rules": [
788-
{
789-
"service_tier": "priority",
790-
"action": "filter",
791-
"scope": "all",
792-
"fallback_action": "pass"
793-
}
794-
]
787+
"rules": []
795788
},
796789
"custom_menu_items": [],
797790
"custom_endpoints": [],
@@ -999,14 +992,7 @@ func TestAPIContracts(t *testing.T) {
999992
"payment_visible_method_wxpay_enabled": false,
1000993
"openai_advanced_scheduler_enabled": false,
1001994
"openai_fast_policy_settings": {
1002-
"rules": [
1003-
{
1004-
"service_tier": "priority",
1005-
"action": "filter",
1006-
"scope": "all",
1007-
"fallback_action": "pass"
1008-
}
1009-
]
995+
"rules": []
1010996
},
1011997
"payment_enabled": false,
1012998
"payment_min_amount": 0,

backend/internal/service/openai_fast_policy_test.go

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/Wei-Shaw/sub2api/internal/config"
1010
"github.com/stretchr/testify/require"
11+
"github.com/tidwall/gjson"
1112
)
1213

1314
type openAIFastPolicyRepoStub struct {
@@ -62,25 +63,33 @@ func newOpenAIGatewayServiceWithSettings(t *testing.T, settings *OpenAIFastPolic
6263
}
6364
}
6465

65-
func TestEvaluateOpenAIFastPolicy_DefaultFiltersAllModelsPriority(t *testing.T) {
66+
func openAIFastFilterPriorityPolicy() *OpenAIFastPolicySettings {
67+
return &OpenAIFastPolicySettings{
68+
Rules: []OpenAIFastPolicyRule{{
69+
ServiceTier: OpenAIFastTierPriority,
70+
Action: BetaPolicyActionFilter,
71+
Scope: BetaPolicyScopeAll,
72+
ModelWhitelist: []string{},
73+
FallbackAction: BetaPolicyActionPass,
74+
}},
75+
}
76+
}
77+
78+
func TestEvaluateOpenAIFastPolicy_DefaultPassesKnownTiers(t *testing.T) {
79+
require.Empty(t, DefaultOpenAIFastPolicySettings().Rules, "default policy must not rewrite service_tier unless admin configured rules")
80+
6681
svc := newOpenAIGatewayServiceWithSettings(t, DefaultOpenAIFastPolicySettings())
6782
account := &Account{Platform: PlatformOpenAI, Type: AccountTypeAPIKey}
6883

69-
// 默认策略对所有模型生效(whitelist 为空),因为 codex 的 service_tier=fast
70-
// 是用户级开关,与 model 正交。
71-
// gpt-5.5 + priority → filter
7284
action, _ := svc.evaluateOpenAIFastPolicy(context.Background(), account, "gpt-5.5", OpenAIFastTierPriority)
73-
require.Equal(t, BetaPolicyActionFilter, action)
85+
require.Equal(t, BetaPolicyActionPass, action)
7486

75-
// gpt-5.5-turbo → filter
7687
action, _ = svc.evaluateOpenAIFastPolicy(context.Background(), account, "gpt-5.5-turbo", OpenAIFastTierPriority)
77-
require.Equal(t, BetaPolicyActionFilter, action)
88+
require.Equal(t, BetaPolicyActionPass, action)
7889

79-
// gpt-4 + priority → filter(默认策略覆盖所有模型)
8090
action, _ = svc.evaluateOpenAIFastPolicy(context.Background(), account, "gpt-4", OpenAIFastTierPriority)
81-
require.Equal(t, BetaPolicyActionFilter, action)
91+
require.Equal(t, BetaPolicyActionPass, action)
8292

83-
// gpt-5.5 + flex → pass (tier doesn't match)
8493
action, _ = svc.evaluateOpenAIFastPolicy(context.Background(), account, "gpt-5.5", OpenAIFastTierFlex)
8594
require.Equal(t, BetaPolicyActionPass, action)
8695

@@ -129,27 +138,24 @@ func TestEvaluateOpenAIFastPolicy_ScopeFiltersOAuth(t *testing.T) {
129138
require.Equal(t, BetaPolicyActionPass, action)
130139
}
131140

132-
func TestApplyOpenAIFastPolicyToBody_FilterRemovesField(t *testing.T) {
141+
func TestApplyOpenAIFastPolicyToBody_DefaultPassesPriorityAndFast(t *testing.T) {
133142
svc := newOpenAIGatewayServiceWithSettings(t, DefaultOpenAIFastPolicySettings())
134143
account := &Account{Platform: PlatformOpenAI, Type: AccountTypeAPIKey}
135144

136-
// gpt-5.5 fast → service_tier stripped
137145
body := []byte(`{"model":"gpt-5.5","service_tier":"priority","messages":[]}`)
138146
updated, err := svc.applyOpenAIFastPolicyToBody(context.Background(), account, "gpt-5.5", body)
139147
require.NoError(t, err)
140-
require.NotContains(t, string(updated), `"service_tier"`)
148+
require.Equal(t, string(body), string(updated))
141149

142-
// Client sending "fast" (alias for priority) also filtered
143150
body = []byte(`{"model":"gpt-5.5","service_tier":"fast"}`)
144151
updated, err = svc.applyOpenAIFastPolicyToBody(context.Background(), account, "gpt-5.5", body)
145152
require.NoError(t, err)
146-
require.NotContains(t, string(updated), `"service_tier"`)
153+
require.Equal(t, "priority", gjson.GetBytes(updated, "service_tier").String())
147154

148-
// gpt-4 priority → 默认策略对所有模型 filter,service_tier 被移除
149155
body = []byte(`{"model":"gpt-4","service_tier":"priority"}`)
150156
updated, err = svc.applyOpenAIFastPolicyToBody(context.Background(), account, "gpt-4", body)
151157
require.NoError(t, err)
152-
require.NotContains(t, string(updated), `"service_tier"`)
158+
require.Equal(t, string(body), string(updated))
153159

154160
// No service_tier → no-op
155161
body = []byte(`{"model":"gpt-5.5"}`)
@@ -158,9 +164,23 @@ func TestApplyOpenAIFastPolicyToBody_FilterRemovesField(t *testing.T) {
158164
require.Equal(t, string(body), string(updated))
159165
}
160166

161-
// TestApplyOpenAIFastPolicyToBody_OfficialTiersBypassDefaultRule 验证扩展白名单后
162-
// 客户端显式发送的 OpenAI 官方合法 tier(auto/default/scale)能透传到上游而不被
163-
// 静默剥离。默认策略只针对 priority,所以这些 tier 落在 fall-through pass 分支。
167+
func TestApplyOpenAIFastPolicyToBody_ExplicitFilterRemovesField(t *testing.T) {
168+
svc := newOpenAIGatewayServiceWithSettings(t, openAIFastFilterPriorityPolicy())
169+
account := &Account{Platform: PlatformOpenAI, Type: AccountTypeAPIKey}
170+
171+
body := []byte(`{"model":"gpt-5.5","service_tier":"priority","messages":[]}`)
172+
updated, err := svc.applyOpenAIFastPolicyToBody(context.Background(), account, "gpt-5.5", body)
173+
require.NoError(t, err)
174+
require.NotContains(t, string(updated), `"service_tier"`)
175+
176+
body = []byte(`{"model":"gpt-5.5","service_tier":"fast"}`)
177+
updated, err = svc.applyOpenAIFastPolicyToBody(context.Background(), account, "gpt-5.5", body)
178+
require.NoError(t, err)
179+
require.NotContains(t, string(updated), `"service_tier"`)
180+
}
181+
182+
// TestApplyOpenAIFastPolicyToBody_OfficialTiersBypassDefaultRule 验证默认配置
183+
// 下客户端显式发送的 OpenAI 官方合法 tier 能透传到上游而不被静默剥离。
164184
func TestApplyOpenAIFastPolicyToBody_OfficialTiersBypassDefaultRule(t *testing.T) {
165185
svc := newOpenAIGatewayServiceWithSettings(t, DefaultOpenAIFastPolicySettings())
166186
account := &Account{Platform: PlatformOpenAI, Type: AccountTypeAPIKey}
@@ -170,10 +190,10 @@ func TestApplyOpenAIFastPolicyToBody_OfficialTiersBypassDefaultRule(t *testing.T
170190
updated, err := svc.applyOpenAIFastPolicyToBody(context.Background(), account, "gpt-5.5", body)
171191
require.NoError(t, err, "tier %q should pass without error", tier)
172192
require.Contains(t, string(updated), `"service_tier":"`+tier+`"`,
173-
"tier %q should be preserved in body under default rule", tier)
193+
"tier %q should be preserved in body under default policy", tier)
174194
}
175195

176-
// evaluate 层也应判定为 pass(默认规则 ServiceTier=priority 与 auto/default/scale 不匹配)
196+
// evaluate 层也应判定为 pass(默认配置没有内置规则)。
177197
for _, tier := range []string{"auto", "default", "scale"} {
178198
action, _ := svc.evaluateOpenAIFastPolicy(context.Background(), account, "gpt-5.5", tier)
179199
require.Equal(t, BetaPolicyActionPass, action, "tier %q should evaluate to pass", tier)

backend/internal/service/openai_fast_policy_ws_test.go

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -22,34 +22,45 @@ import (
2222

2323
// --- Helper-level (unit) tests for applyOpenAIFastPolicyToWSResponseCreate ---
2424

25-
func TestWSResponseCreate_FilterStripsServiceTier(t *testing.T) {
25+
func TestWSResponseCreate_DefaultPassesPriorityAndNormalizesFast(t *testing.T) {
2626
svc := newOpenAIGatewayServiceWithSettings(t, DefaultOpenAIFastPolicySettings())
2727
account := &Account{Platform: PlatformOpenAI, Type: AccountTypeAPIKey}
2828

2929
frame := []byte(`{"type":"response.create","model":"gpt-5.5","service_tier":"priority","input":[{"type":"input_text","text":"hi"}]}`)
3030
updated, blocked, err := svc.applyOpenAIFastPolicyToWSResponseCreate(context.Background(), account, "gpt-5.5", frame)
3131
require.NoError(t, err)
3232
require.Nil(t, blocked)
33-
require.NotContains(t, string(updated), `"service_tier"`, "filter action should strip service_tier")
33+
require.Equal(t, "priority", gjson.GetBytes(updated, "service_tier").String(), "default policy should preserve priority tier")
3434
// Other fields preserved.
3535
require.Equal(t, "response.create", gjson.GetBytes(updated, "type").String())
3636
require.Equal(t, "gpt-5.5", gjson.GetBytes(updated, "model").String())
3737
require.Equal(t, "hi", gjson.GetBytes(updated, "input.0.text").String())
38+
39+
frame = []byte(`{"type":"response.create","model":"gpt-5.5","service_tier":"fast"}`)
40+
updated, blocked, err = svc.applyOpenAIFastPolicyToWSResponseCreate(context.Background(), account, "gpt-5.5", frame)
41+
require.NoError(t, err)
42+
require.Nil(t, blocked)
43+
require.Equal(t, "priority", gjson.GetBytes(updated, "service_tier").String(), "fast alias should normalize before reaching upstream")
44+
45+
// Mixed-case + whitespace variant should also normalize.
46+
frame = []byte(`{"type":"response.create","model":"gpt-5.5","service_tier":" Fast "}`)
47+
updated, blocked, err = svc.applyOpenAIFastPolicyToWSResponseCreate(context.Background(), account, "gpt-5.5", frame)
48+
require.NoError(t, err)
49+
require.Nil(t, blocked)
50+
require.Equal(t, "priority", gjson.GetBytes(updated, "service_tier").String())
3851
}
3952

40-
func TestWSResponseCreate_FastNormalizedToPriorityThenFiltered(t *testing.T) {
41-
svc := newOpenAIGatewayServiceWithSettings(t, DefaultOpenAIFastPolicySettings())
53+
func TestWSResponseCreate_ExplicitFilterStripsServiceTier(t *testing.T) {
54+
svc := newOpenAIGatewayServiceWithSettings(t, openAIFastFilterPriorityPolicy())
4255
account := &Account{Platform: PlatformOpenAI, Type: AccountTypeAPIKey}
4356

44-
// Verbatim "fast" → normalized to "priority" → matches default rule → filter.
45-
frame := []byte(`{"type":"response.create","model":"gpt-5.5","service_tier":"fast"}`)
57+
frame := []byte(`{"type":"response.create","model":"gpt-5.5","service_tier":"priority","input":[{"type":"input_text","text":"hi"}]}`)
4658
updated, blocked, err := svc.applyOpenAIFastPolicyToWSResponseCreate(context.Background(), account, "gpt-5.5", frame)
4759
require.NoError(t, err)
4860
require.Nil(t, blocked)
49-
require.NotContains(t, string(updated), `"service_tier"`)
61+
require.NotContains(t, string(updated), `"service_tier"`, "filter action should strip service_tier")
5062

51-
// Mixed-case + whitespace variant should also normalize and filter.
52-
frame = []byte(`{"type":"response.create","model":"gpt-5.5","service_tier":" Fast "}`)
63+
frame = []byte(`{"type":"response.create","model":"gpt-5.5","service_tier":"fast"}`)
5364
updated, blocked, err = svc.applyOpenAIFastPolicyToWSResponseCreate(context.Background(), account, "gpt-5.5", frame)
5465
require.NoError(t, err)
5566
require.Nil(t, blocked)
@@ -60,7 +71,7 @@ func TestWSResponseCreate_FlexPassThrough(t *testing.T) {
6071
svc := newOpenAIGatewayServiceWithSettings(t, DefaultOpenAIFastPolicySettings())
6172
account := &Account{Platform: PlatformOpenAI, Type: AccountTypeAPIKey}
6273

63-
// Default policy targets priority only; flex is left untouched.
74+
// Default policy has no rules; flex is left untouched.
6475
frame := []byte(`{"type":"response.create","model":"gpt-5.5","service_tier":"flex"}`)
6576
updated, blocked, err := svc.applyOpenAIFastPolicyToWSResponseCreate(context.Background(), account, "gpt-5.5", frame)
6677
require.NoError(t, err)
@@ -220,8 +231,8 @@ func (f *fakePassthroughFrameConn) Close() error {
220231
}
221232

222233
// gpt55WhitelistFastPolicy 返回一份强制带 model whitelist 的策略,用于
223-
// 验证 capturedSessionModel fallback 的语义(默认策略 whitelist 为空时
224-
// fallback 路径无法被观察到)。
234+
// 验证 capturedSessionModel fallback 的语义(默认配置没有规则,fallback
235+
// 路径无法被观察到)。
225236
func gpt55WhitelistFastPolicy() *OpenAIFastPolicySettings {
226237
return &OpenAIFastPolicySettings{
227238
Rules: []OpenAIFastPolicyRule{{
@@ -242,7 +253,7 @@ func gpt55WhitelistFastPolicy() *OpenAIFastPolicySettings {
242253
// through to the upstream.
243254
func TestPolicyEnforcingFrameConn_FollowupFrameWithoutModelUsesCapturedModel(t *testing.T) {
244255
// 此处特意使用带 whitelist 的策略,以便观察 capturedSessionModel
245-
// fallback 是否生效(默认策略 whitelist 为空,fallback 与否结果一致,
256+
// fallback 是否生效(默认配置没有规则,fallback 与否结果一致,
246257
// 不能用来覆盖此回归)。
247258
svc := newOpenAIGatewayServiceWithSettings(t, gpt55WhitelistFastPolicy())
248259
account := &Account{Platform: PlatformOpenAI, Type: AccountTypeAPIKey}
@@ -310,13 +321,13 @@ func TestPolicyEnforcingFrameConn_WithoutCapturedFallbackPolicyMisses(t *testing
310321
"sanity: without capturedSessionModel fallback the leak (D5) reproduces — confirms the fix is load-bearing")
311322
}
312323

313-
// --- Ingress end-to-end test (filter path) ---
324+
// --- Ingress end-to-end test (explicit filter path) ---
314325

315326
// TestWSResponseCreate_IngressFiltersServiceTierBeforeUpstream wires up the
316327
// real ProxyResponsesWebSocketFromClient ingress session pipeline against a
317328
// captureConn upstream and asserts that a client frame with service_tier=fast
318-
// is normalized + filtered out before being written upstream. This is the
319-
// integration flavour of TestWSResponseCreate_FilterStripsServiceTier.
329+
// is normalized + filtered out by an explicit admin policy before being
330+
// written upstream.
320331
func TestWSResponseCreate_IngressFiltersServiceTierBeforeUpstream(t *testing.T) {
321332
gin.SetMode(gin.TestMode)
322333

@@ -345,9 +356,9 @@ func TestWSResponseCreate_IngressFiltersServiceTierBeforeUpstream(t *testing.T)
345356
pool.setClientDialerForTest(captureDialer)
346357

347358
repo := &openAIFastPolicyRepoStub{values: map[string]string{}}
348-
defaultJSON, err := json.Marshal(DefaultOpenAIFastPolicySettings())
359+
filterPolicyJSON, err := json.Marshal(openAIFastFilterPriorityPolicy())
349360
require.NoError(t, err)
350-
repo.values[SettingKeyOpenAIFastPolicySettings] = string(defaultJSON)
361+
repo.values[SettingKeyOpenAIFastPolicySettings] = string(filterPolicyJSON)
351362

352363
svc := &OpenAIGatewayService{
353364
cfg: cfg,
@@ -631,13 +642,13 @@ func TestApplyOpenAIFastPolicyToBody_BlockShortCircuitsUpstream(t *testing.T) {
631642
require.Equal(t, string(body), string(updated), "block must not mutate body")
632643
}
633644

634-
// TestForwardAsAnthropicMessages_BetaFastModeTriggersOpenAIFastPolicy verifies
635-
// the Anthropic-compat entrypoint chain: anthropic-beta: fast-mode → BetaFastMode
636-
// detection → ServiceTier="priority" injection (openai_gateway_messages.go:60)
637-
// → applyOpenAIFastPolicyToBody filter on default policy → upstream body has
638-
// no service_tier. We exercise the same internal pipeline (Anthropic→Responses
639-
// + BetaFastMode + policy) without spinning up a real upstream HTTP server.
640-
func TestForwardAsAnthropicMessages_BetaFastModeTriggersOpenAIFastPolicy(t *testing.T) {
645+
// TestForwardAsAnthropicMessages_BetaFastModePassesOpenAIFastPolicyByDefault
646+
// verifies the Anthropic-compat entrypoint chain: anthropic-beta: fast-mode →
647+
// BetaFastMode detection → ServiceTier="priority" injection
648+
// (openai_gateway_messages.go:60) → default OpenAI fast policy pass. We
649+
// exercise the same internal pipeline (Anthropic→Responses + BetaFastMode +
650+
// policy) without spinning up a real upstream HTTP server.
651+
func TestForwardAsAnthropicMessages_BetaFastModePassesOpenAIFastPolicyByDefault(t *testing.T) {
641652
svc := newOpenAIGatewayServiceWithSettings(t, DefaultOpenAIFastPolicySettings())
642653
account := &Account{Platform: PlatformOpenAI, Type: AccountTypeAPIKey}
643654

@@ -663,8 +674,9 @@ func TestForwardAsAnthropicMessages_BetaFastModeTriggersOpenAIFastPolicy(t *test
663674
upstreamBody, policyErr := svc.applyOpenAIFastPolicyToBody(context.Background(), account, "gpt-5.5", responsesBody)
664675
require.NoError(t, policyErr)
665676

666-
// Step 4: assert that policy filtered the field before the upstream HTTP request.
667-
require.NotContains(t, string(upstreamBody), `"service_tier"`, "default policy 命中 gpt-5.5 priority 应当 filter 掉 service_tier")
677+
// Step 4: default policy must preserve the explicit fast/priority request.
678+
require.Equal(t, "priority", gjson.GetBytes(upstreamBody, "service_tier").String(),
679+
"default policy should pass service_tier=priority through to upstream")
668680
}
669681

670682
// --- Fix1: passthrough capturedSessionModel must follow session.update ---
@@ -808,7 +820,7 @@ func TestApplyOpenAIFastPolicyToBody_PassNormalizesFastAlias(t *testing.T) {
808820
// tier) instead of the user-requested "priority". This test pins the
809821
// contract those two helpers must uphold for the adapter's billing path.
810822
func TestPassthroughBilling_PostFilterServiceTier(t *testing.T) {
811-
svc := newOpenAIGatewayServiceWithSettings(t, DefaultOpenAIFastPolicySettings())
823+
svc := newOpenAIGatewayServiceWithSettings(t, openAIFastFilterPriorityPolicy())
812824
account := &Account{Platform: PlatformOpenAI, Type: AccountTypeAPIKey}
813825

814826
raw := []byte(`{"type":"response.create","model":"gpt-5.5","service_tier":"priority"}`)
@@ -821,7 +833,7 @@ func TestPassthroughBilling_PostFilterServiceTier(t *testing.T) {
821833
require.Equal(t, "priority", *pre,
822834
"sanity: raw first frame carries priority that pre-fix billing would have reported")
823835

824-
// Apply policy filter (default rule: gpt-5.5 + priority → filter).
836+
// Apply explicit policy filter (gpt-5.5 + priority → filter).
825837
filtered, blocked, err := svc.applyOpenAIFastPolicyToWSResponseCreate(context.Background(), account, "gpt-5.5", raw)
826838
require.NoError(t, err)
827839
require.Nil(t, blocked)
@@ -890,17 +902,17 @@ func TestApplyOpenAIFastPolicyToBody_NonStringServiceTier(t *testing.T) {
890902
// atomic.Pointer[string] on every successful response.create frame.
891903
//
892904
// This test pins the four legs of the semantic contract:
893-
// - turn 1: service_tier=priority hits the default whitelist filter, so
905+
// - turn 1: service_tier=priority hits the explicit filter rule, so
894906
// after filter the upstream sees no tier → billing is nil.
895-
// - turn 2: service_tier=flex passes (default rule targets priority only),
907+
// - turn 2: service_tier=flex passes (the filter rule targets priority only),
896908
// billing should now reflect "flex".
897909
// - turn 3: response.create without any service_tier — the upstream will
898910
// treat it as default; we choose to mirror that and overwrite billing
899911
// to nil rather than carry over "flex" from turn 2.
900912
// - non-response.create frame (response.cancel here) carrying a stray
901913
// service_tier-shaped field must NOT clobber the billing pointer.
902914
func TestPassthroughBilling_MultiTurnServiceTierFollowsFilteredFrames(t *testing.T) {
903-
svc := newOpenAIGatewayServiceWithSettings(t, DefaultOpenAIFastPolicySettings())
915+
svc := newOpenAIGatewayServiceWithSettings(t, openAIFastFilterPriorityPolicy())
904916
account := &Account{Platform: PlatformOpenAI, Type: AccountTypeAPIKey}
905917

906918
// Mirror the production filter closure (openai_ws_v2_passthrough_adapter.go

backend/internal/service/openai_gateway_service.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6098,7 +6098,7 @@ func writeOpenAIFastPolicyBlockedResponse(c *gin.Context, err *OpenAIFastBlocked
60986098
// applyOpenAIFastPolicyToBody contract but operates on a Realtime/Responses
60996099
// WS payload:
61006100
//
6101-
// - pass: returns frame unchanged (newBytes == frame, blocked == nil)
6101+
// - pass: keeps service_tier, normalizing aliases such as "fast" to "priority"
61026102
// - filter: returns a copy with top-level service_tier removed
61036103
// - block: returns (frame, *OpenAIFastBlockedError)
61046104
//
@@ -6162,7 +6162,14 @@ func (s *OpenAIGatewayService) applyOpenAIFastPolicyToWSResponseCreate(
61626162
}
61636163
return trimmed, nil, nil
61646164
default:
6165-
return frame, nil, nil
6165+
if normTier == rawTier {
6166+
return frame, nil, nil
6167+
}
6168+
updated, err := sjson.SetBytes(frame, "service_tier", normTier)
6169+
if err != nil {
6170+
return frame, nil, fmt.Errorf("normalize service_tier in ws frame: %w", err)
6171+
}
6172+
return updated, nil, nil
61666173
}
61676174
}
61686175

0 commit comments

Comments
 (0)