Merge pull request #226 from CJackHwang/codex/fix-issue-with-passing-thresholds

Handle deferred Close errors, normalize error messages, and add nolint annotations
This commit is contained in:
CJACK.
2026-04-06 13:54:14 +08:00
committed by GitHub
42 changed files with 92 additions and 63 deletions

View File

@@ -30,8 +30,8 @@ func main() {
opts.Timeout = time.Duration(timeoutSeconds) * time.Second
if err := testsuite.Run(context.Background(), opts); err != nil {
fmt.Fprintln(os.Stderr, err.Error())
_, _ = fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}
fmt.Fprintln(os.Stdout, "testsuite completed successfully")
_, _ = fmt.Fprintln(os.Stdout, "testsuite completed successfully")
}

View File

@@ -64,7 +64,7 @@ func (h *Handler) proxyViaOpenAI(w http.ResponseWriter, r *http.Request, store C
rec := httptest.NewRecorder()
h.OpenAI.ChatCompletions(rec, proxyReq)
res := rec.Result()
defer res.Body.Close()
defer func() { _ = res.Body.Close() }()
body, _ := io.ReadAll(res.Body)
for k, vv := range res.Header {
for _, v := range vv {
@@ -94,7 +94,7 @@ func (h *Handler) proxyViaOpenAI(w http.ResponseWriter, r *http.Request, store C
rec := httptest.NewRecorder()
h.OpenAI.ChatCompletions(rec, proxyReq)
res := rec.Result()
defer res.Body.Close()
defer func() { _ = res.Body.Close() }()
body, _ := io.ReadAll(res.Body)
if res.StatusCode < 200 || res.StatusCode >= 300 {
for k, vv := range res.Header {
@@ -124,7 +124,7 @@ func (h *Handler) proxyViaOpenAI(w http.ResponseWriter, r *http.Request, store C
}
func (h *Handler) handleClaudeStreamRealtime(w http.ResponseWriter, r *http.Request, resp *http.Response, model string, messages []any, thinkingEnabled, searchEnabled bool, toolNames []string) {
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
writeClaudeError(w, http.StatusInternalServerError, string(body))

View File

@@ -101,6 +101,7 @@ func buildClaudeToolPrompt(tools []any) string {
toolcall.BuildToolCallInstructions(names)
}
//nolint:unused // retained for compatibility with pending Claude tool-result prompt flow.
func formatClaudeToolResultForPrompt(block map[string]any) string {
if block == nil {
return ""

View File

@@ -96,6 +96,7 @@ func looksLikeBase64Payload(v string) bool {
return true
}
//nolint:unused // helper kept for compatibility with upcoming sanitize pipeline.
func marshalCompactJSON(v any) string {
b, err := json.Marshal(v)
if err != nil {

View File

@@ -18,7 +18,7 @@ func normalizeClaudeRequest(store ConfigReader, req map[string]any) (claudeNorma
model, _ := req["model"].(string)
messagesRaw, _ := req["messages"].([]any)
if strings.TrimSpace(model) == "" || len(messagesRaw) == 0 {
return claudeNormalizedRequest{}, fmt.Errorf("Request must include 'model' and 'messages'.")
return claudeNormalizedRequest{}, fmt.Errorf("request must include 'model' and 'messages'")
}
if _, ok := req["max_tokens"]; !ok {
req["max_tokens"] = 8192

View File

@@ -5,6 +5,7 @@ import (
"strings"
)
//nolint:unused // compatibility hook for native Gemini request normalization path.
func collectGeminiPassThrough(req map[string]any) map[string]any {
cfg, _ := req["generationConfig"].(map[string]any)
if len(cfg) == 0 {

View File

@@ -9,6 +9,7 @@ import (
"ds2api/internal/util"
)
//nolint:unused // kept for native Gemini adapter route compatibility.
func normalizeGeminiRequest(store ConfigReader, routeModel string, req map[string]any, stream bool) (util.StandardRequest, error) {
requestedModel := strings.TrimSpace(routeModel)
if requestedModel == "" {
@@ -17,13 +18,13 @@ func normalizeGeminiRequest(store ConfigReader, routeModel string, req map[strin
resolvedModel, ok := config.ResolveModel(store, requestedModel)
if !ok {
return util.StandardRequest{}, fmt.Errorf("Model '%s' is not available.", requestedModel)
return util.StandardRequest{}, fmt.Errorf("model %q is not available", requestedModel)
}
thinkingEnabled, searchEnabled, _ := config.GetModelConfig(resolvedModel)
messagesRaw := geminiMessagesFromRequest(req)
if len(messagesRaw) == 0 {
return util.StandardRequest{}, fmt.Errorf("Request must include non-empty contents.")
return util.StandardRequest{}, fmt.Errorf("request must include non-empty contents")
}
toolsRaw := convertGeminiTools(req["tools"])

View File

@@ -2,6 +2,7 @@ package gemini
import "strings"
//nolint:unused // kept for native Gemini adapter route compatibility.
func convertGeminiTools(raw any) []any {
tools, _ := raw.([]any)
if len(tools) == 0 {

View File

@@ -58,7 +58,7 @@ func (h *Handler) proxyViaOpenAI(w http.ResponseWriter, r *http.Request, stream
rec := httptest.NewRecorder()
h.OpenAI.ChatCompletions(rec, proxyReq)
res := rec.Result()
defer res.Body.Close()
defer func() { _ = res.Body.Close() }()
body, _ := io.ReadAll(res.Body)
for k, vv := range res.Header {
for _, v := range vv {
@@ -88,7 +88,7 @@ func (h *Handler) proxyViaOpenAI(w http.ResponseWriter, r *http.Request, stream
rec := httptest.NewRecorder()
h.OpenAI.ChatCompletions(rec, proxyReq)
res := rec.Result()
defer res.Body.Close()
defer func() { _ = res.Body.Close() }()
body, _ := io.ReadAll(res.Body)
if res.StatusCode < 200 || res.StatusCode >= 300 {
for k, vv := range res.Header {
@@ -132,8 +132,9 @@ func writeGeminiErrorFromOpenAI(w http.ResponseWriter, status int, raw []byte) {
writeGeminiError(w, status, message)
}
//nolint:unused // retained for native Gemini non-stream handling path.
func (h *Handler) handleNonStreamGenerateContent(w http.ResponseWriter, resp *http.Response, model, finalPrompt string, thinkingEnabled bool, toolNames []string) {
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
writeGeminiError(w, resp.StatusCode, strings.TrimSpace(string(body)))
@@ -152,6 +153,7 @@ func (h *Handler) handleNonStreamGenerateContent(w http.ResponseWriter, resp *ht
))
}
//nolint:unused // retained for native Gemini non-stream handling path.
func buildGeminiGenerateContentResponse(model, finalPrompt, finalThinking, finalText string, toolNames []string, outputTokens int) map[string]any {
parts := buildGeminiPartsFromFinal(finalText, finalThinking, toolNames)
usage := buildGeminiUsage(finalPrompt, finalThinking, finalText, outputTokens)
@@ -171,6 +173,7 @@ func buildGeminiGenerateContentResponse(model, finalPrompt, finalThinking, final
}
}
//nolint:unused // retained for native Gemini non-stream handling path.
func buildGeminiUsage(finalPrompt, finalThinking, finalText string, outputTokens int) map[string]any {
promptTokens := util.EstimateTokens(finalPrompt)
reasoningTokens := util.EstimateTokens(finalThinking)
@@ -186,6 +189,7 @@ func buildGeminiUsage(finalPrompt, finalThinking, finalText string, outputTokens
}
}
//nolint:unused // retained for native Gemini non-stream handling path.
func buildGeminiPartsFromFinal(finalText, finalThinking string, toolNames []string) []map[string]any {
detected := toolcall.ParseToolCalls(finalText, toolNames)
if len(detected) == 0 && finalThinking != "" {

View File

@@ -17,6 +17,7 @@ type Handler struct {
OpenAI OpenAIChatRunner
}
//nolint:unused // used by native Gemini stream/non-stream runtime helpers.
func (h *Handler) compatStripReferenceMarkers() bool {
if h == nil || h.Store == nil {
return true

View File

@@ -12,8 +12,9 @@ import (
streamengine "ds2api/internal/stream"
)
//nolint:unused // retained for native Gemini stream handling path.
func (h *Handler) handleStreamGenerateContent(w http.ResponseWriter, r *http.Request, resp *http.Response, model, finalPrompt string, thinkingEnabled, searchEnabled bool, toolNames []string) {
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
writeGeminiError(w, resp.StatusCode, strings.TrimSpace(string(body)))
@@ -49,6 +50,7 @@ func (h *Handler) handleStreamGenerateContent(w http.ResponseWriter, r *http.Req
})
}
//nolint:unused // retained for native Gemini stream handling path.
type geminiStreamRuntime struct {
w http.ResponseWriter
rc *http.ResponseController
@@ -68,6 +70,7 @@ type geminiStreamRuntime struct {
outputTokens int
}
//nolint:unused // retained for native Gemini stream handling path.
func newGeminiStreamRuntime(
w http.ResponseWriter,
rc *http.ResponseController,
@@ -93,6 +96,7 @@ func newGeminiStreamRuntime(
}
}
//nolint:unused // retained for native Gemini stream handling path.
func (s *geminiStreamRuntime) sendChunk(payload map[string]any) {
b, _ := json.Marshal(payload)
_, _ = s.w.Write([]byte("data: "))
@@ -103,6 +107,7 @@ func (s *geminiStreamRuntime) sendChunk(payload map[string]any) {
}
}
//nolint:unused // retained for native Gemini stream handling path.
func (s *geminiStreamRuntime) onParsed(parsed sse.LineResult) streamengine.ParsedDecision {
if !parsed.Parsed {
return streamengine.ParsedDecision{}
@@ -158,6 +163,7 @@ func (s *geminiStreamRuntime) onParsed(parsed sse.LineResult) streamengine.Parse
return streamengine.ParsedDecision{ContentSeen: contentSeen}
}
//nolint:unused // retained for native Gemini stream handling path.
func (s *geminiStreamRuntime) finalize() {
finalThinking := s.thinking.String()
finalText := cleanVisibleOutput(s.text.String(), s.stripReferenceMarkers)

View File

@@ -42,19 +42,23 @@ func (m testGeminiAuth) Determine(_ *http.Request) (*auth.RequestAuth, error) {
func (testGeminiAuth) Release(_ *auth.RequestAuth) {}
//nolint:unused // reserved test double for native Gemini DS-call path coverage.
type testGeminiDS struct {
resp *http.Response
err error
}
//nolint:unused // reserved test double for native Gemini DS-call path coverage.
func (m testGeminiDS) CreateSession(_ context.Context, _ *auth.RequestAuth, _ int) (string, error) {
return "session-id", nil
}
//nolint:unused // reserved test double for native Gemini DS-call path coverage.
func (m testGeminiDS) GetPow(_ context.Context, _ *auth.RequestAuth, _ int) (string, error) {
return "pow", nil
}
//nolint:unused // reserved test double for native Gemini DS-call path coverage.
func (m testGeminiDS) CallCompletion(_ context.Context, _ *auth.RequestAuth, _ map[string]any, _ string, _ int) (*http.Response, error) {
if m.err != nil {
return nil, m.err
@@ -100,6 +104,7 @@ func (s geminiOpenAISuccessStub) ChatCompletions(w http.ResponseWriter, _ *http.
_, _ = w.Write([]byte(out))
}
//nolint:unused // helper retained for native Gemini stream fixture tests.
func makeGeminiUpstreamResponse(lines ...string) *http.Response {
body := strings.Join(lines, "\n")
if !strings.HasSuffix(body, "\n") {

View File

@@ -2,6 +2,7 @@ package gemini
import textclean "ds2api/internal/textclean"
//nolint:unused // retained for native Gemini output post-processing path.
func cleanVisibleOutput(text string, stripReferenceMarkers bool) string {
if text == "" {
return text

View File

@@ -116,7 +116,7 @@ func (h *Handler) autoDeleteRemoteSession(ctx context.Context, a *auth.RequestAu
func (h *Handler) handleNonStream(w http.ResponseWriter, ctx context.Context, resp *http.Response, completionID, model, finalPrompt string, thinkingEnabled bool, toolNames []string) {
if resp.StatusCode != http.StatusOK {
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
body, _ := io.ReadAll(resp.Body)
writeOpenAIError(w, resp.StatusCode, string(body))
return
@@ -143,7 +143,7 @@ func (h *Handler) handleNonStream(w http.ResponseWriter, ctx context.Context, re
}
func (h *Handler) handleStream(w http.ResponseWriter, r *http.Request, resp *http.Response, completionID, model, finalPrompt string, thinkingEnabled, searchEnabled bool, toolNames []string) {
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
writeOpenAIError(w, resp.StatusCode, string(body))

View File

@@ -107,7 +107,7 @@ type autoDeleteCtxDSStub struct {
}
func (m *autoDeleteCtxDSStub) DeleteSessionForToken(ctx context.Context, token string, sessionID string) (*deepseek.DeleteSessionResult, error) {
return m.autoDeleteModeDSStub.DeleteSessionForTokenCtx(ctx, token, sessionID)
return m.DeleteSessionForTokenCtx(ctx, token, sessionID)
}
func (m *autoDeleteCtxDSStub) DeleteAllSessionsForToken(_ context.Context, _ string) error {

View File

@@ -107,7 +107,7 @@ func (h *Handler) Responses(w http.ResponseWriter, r *http.Request) {
}
func (h *Handler) handleResponsesNonStream(w http.ResponseWriter, resp *http.Response, owner, responseID, model, finalPrompt string, thinkingEnabled bool, toolNames []string, toolChoice util.ToolChoicePolicy, traceID string) {
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
writeOpenAIError(w, resp.StatusCode, strings.TrimSpace(string(body)))
@@ -143,7 +143,7 @@ func (h *Handler) handleResponsesNonStream(w http.ResponseWriter, resp *http.Res
}
func (h *Handler) handleResponsesStream(w http.ResponseWriter, r *http.Request, resp *http.Response, owner, responseID, model, finalPrompt string, thinkingEnabled, searchEnabled bool, toolNames []string, toolChoice util.ToolChoicePolicy, traceID string) {
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
writeOpenAIError(w, resp.StatusCode, strings.TrimSpace(string(body)))

View File

@@ -12,11 +12,11 @@ func normalizeOpenAIChatRequest(store ConfigReader, req map[string]any, traceID
model, _ := req["model"].(string)
messagesRaw, _ := req["messages"].([]any)
if strings.TrimSpace(model) == "" || len(messagesRaw) == 0 {
return util.StandardRequest{}, fmt.Errorf("Request must include 'model' and 'messages'.")
return util.StandardRequest{}, fmt.Errorf("request must include 'model' and 'messages'")
}
resolvedModel, ok := config.ResolveModel(store, model)
if !ok {
return util.StandardRequest{}, fmt.Errorf("Model '%s' is not available.", model)
return util.StandardRequest{}, fmt.Errorf("model %q is not available", model)
}
thinkingEnabled, searchEnabled, _ := config.GetModelConfig(resolvedModel)
responseModel := strings.TrimSpace(model)
@@ -48,11 +48,11 @@ func normalizeOpenAIResponsesRequest(store ConfigReader, req map[string]any, tra
model, _ := req["model"].(string)
model = strings.TrimSpace(model)
if model == "" {
return util.StandardRequest{}, fmt.Errorf("Request must include 'model'.")
return util.StandardRequest{}, fmt.Errorf("request must include 'model'")
}
resolvedModel, ok := config.ResolveModel(store, model)
if !ok {
return util.StandardRequest{}, fmt.Errorf("Model '%s' is not available.", model)
return util.StandardRequest{}, fmt.Errorf("model %q is not available", model)
}
thinkingEnabled, searchEnabled, _ := config.GetModelConfig(resolvedModel)
@@ -68,7 +68,7 @@ func normalizeOpenAIResponsesRequest(store ConfigReader, req map[string]any, tra
messagesRaw = msgs
}
if len(messagesRaw) == 0 {
return util.StandardRequest{}, fmt.Errorf("Request must include 'input' or 'messages'.")
return util.StandardRequest{}, fmt.Errorf("request must include 'input' or 'messages'")
}
toolPolicy, err := parseToolChoicePolicy(req["tool_choice"], req["tools"])
if err != nil {
@@ -152,7 +152,7 @@ func parseToolChoicePolicy(toolChoiceRaw any, toolsRaw any) (util.ToolChoicePoli
case "required":
policy.Mode = util.ToolChoiceRequired
default:
return util.ToolChoicePolicy{}, fmt.Errorf("Unsupported tool_choice: %q", v)
return util.ToolChoicePolicy{}, fmt.Errorf("unsupported tool_choice: %q", v)
}
case map[string]any:
allowedOverride, hasAllowedOverride, err := parseAllowedToolNames(v["allowed_tools"])
@@ -198,7 +198,7 @@ func parseToolChoicePolicy(toolChoiceRaw any, toolsRaw any) (util.ToolChoicePoli
policy.ForcedName = name
policy.Allowed = namesToSet([]string{name})
default:
return util.ToolChoicePolicy{}, fmt.Errorf("Unsupported tool_choice.type: %q", typ)
return util.ToolChoicePolicy{}, fmt.Errorf("unsupported tool_choice.type: %q", typ)
}
default:
return util.ToolChoicePolicy{}, fmt.Errorf("tool_choice must be a string or object")
@@ -206,7 +206,7 @@ func parseToolChoicePolicy(toolChoiceRaw any, toolsRaw any) (util.ToolChoicePoli
if policy.Mode == util.ToolChoiceRequired || policy.Mode == util.ToolChoiceForced {
if len(declaredNames) == 0 {
return util.ToolChoicePolicy{}, fmt.Errorf("tool_choice=%s requires non-empty tools.", policy.Mode)
return util.ToolChoicePolicy{}, fmt.Errorf("tool_choice=%s requires non-empty tools", policy.Mode)
}
}
if policy.Mode == util.ToolChoiceForced {

View File

@@ -8,6 +8,7 @@ import (
// --- XML tool call support for the streaming sieve ---
//nolint:unused // kept as explicit tag inventory for future XML sieve refinements.
var xmlToolCallClosingTags = []string{"</tool_calls>", "</tool_call>", "</invoke>", "</function_call>", "</function_calls>", "</tool_use>",
// Agent-style XML tags (Roo Code, Cline, etc.)
"</attempt_completion>", "</ask_followup_question>", "</new_task>", "</result>"}
@@ -33,6 +34,8 @@ var xmlToolCallTagPairs = []struct{ open, close string }{
}
// xmlToolCallBlockPattern matches a complete XML tool call block (wrapper or standalone).
//
//nolint:unused // reserved for future fast-path XML block detection.
var xmlToolCallBlockPattern = regexp.MustCompile(`(?is)(<tool_calls>\s*(?:.*?)\s*</tool_calls>|<tool_call>\s*(?:.*?)\s*</tool_call>|<invoke\b[^>]*>(?:.*?)</invoke>|<function_calls?\b[^>]*>(?:.*?)</function_calls?>|<tool_use>(?:.*?)</tool_use>|<attempt_completion>(?:.*?)</attempt_completion>|<ask_followup_question>(?:.*?)</ask_followup_question>|<new_task>(?:.*?)</new_task>)`)
// xmlToolTagsToDetect is the set of XML tag prefixes used by findToolSegmentStart.

View File

@@ -165,7 +165,7 @@ func (h *Handler) testAccount(ctx context.Context, acc config.Account, model, me
return result
}
if resp.StatusCode != http.StatusOK {
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
result["message"] = fmt.Sprintf("请求失败: HTTP %d", resp.StatusCode)
return result
}
@@ -218,7 +218,7 @@ func (h *Handler) testAPI(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"success": false, "error": err.Error()})
return
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode == http.StatusOK {
var parsed any

View File

@@ -85,7 +85,7 @@ func (h *Handler) addKey(w http.ResponseWriter, r *http.Request) {
err := h.Store.Update(func(c *config.Config) error {
for _, k := range c.Keys {
if k == key {
return fmt.Errorf("Key 已存在")
return fmt.Errorf("key 已存在")
}
}
c.Keys = append(c.Keys, key)
@@ -109,7 +109,7 @@ func (h *Handler) deleteKey(w http.ResponseWriter, r *http.Request) {
}
}
if idx < 0 {
return fmt.Errorf("Key 不存在")
return fmt.Errorf("key 不存在")
}
c.Keys = append(c.Keys[:idx], c.Keys[idx+1:]...)
return nil

View File

@@ -301,7 +301,7 @@ func vercelRequest(ctx context.Context, client *http.Client, method, endpoint st
if err != nil {
return nil, 0, err
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
b, _ := io.ReadAll(resp.Body)
parsed := map[string]any{}
_ = json.Unmarshal(b, &parsed)

View File

@@ -43,7 +43,7 @@ func (h *Handler) getVersion(w http.ResponseWriter, _ *http.Request) {
writeJSON(w, http.StatusOK, resp)
return
}
defer r.Body.Close()
defer func() { _ = r.Body.Close() }()
if r.StatusCode < 200 || r.StatusCode >= 300 {
resp["check_error"] = "github api status: " + r.Status
writeJSON(w, http.StatusOK, resp)

View File

@@ -130,9 +130,7 @@ func TestMarkTokenInvalidNotConfigToken(t *testing.T) {
a := &RequestAuth{UseConfigToken: false, DeepSeekToken: "direct", resolver: r}
r.MarkTokenInvalid(a)
// Should not panic, token should be unchanged for non-config
if a.DeepSeekToken != "" {
// Actually it does clear it; that's fine - let's check behavior
}
_ = a.DeepSeekToken // Actual behavior may clear it; this test only asserts no panic.
}
func TestMarkTokenInvalidEmptyAccountID(t *testing.T) {

View File

@@ -58,8 +58,7 @@ func TestLoadStorePreservesFileBackedTokensForRuntime(t *testing.T) {
if err != nil {
t.Fatalf("create temp config: %v", err)
}
defer tmp.Close()
defer func() { _ = tmp.Close() }()
if _, err := tmp.WriteString(`{
"accounts":[{"email":"u@example.com","password":"p","token":"persisted-token"}]
}`); err != nil {
@@ -355,7 +354,7 @@ func TestAccountTestStatusIsRuntimeOnlyAndNotPersisted(t *testing.T) {
if err != nil {
t.Fatalf("create temp config: %v", err)
}
defer tmp.Close()
defer func() { _ = tmp.Close() }()
if _, err := tmp.WriteString(`{
"accounts":[{"email":"u@example.com","password":"p","test_status":"ok"}]
}`); err != nil {

View File

@@ -54,8 +54,7 @@ func TestCallContinuePropagatesPowHeaderToFallbackRequest(t *testing.T) {
if err != nil {
t.Fatalf("callContinue returned error: %v", err)
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
if seenPow != "pow-response-abc" {
t.Fatalf("continue request pow header=%q want=%q", seenPow, "pow-response-abc")
}
@@ -105,8 +104,7 @@ func TestCallCompletionAutoContinueThreadsPowHeader(t *testing.T) {
if err != nil {
t.Fatalf("CallCompletion returned error: %v", err)
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
out, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("read auto-continued body failed: %v", err)

View File

@@ -19,7 +19,7 @@ func readResponseBody(resp *http.Response) ([]byte, error) {
if err != nil {
return nil, err
}
defer gz.Close()
defer func() { _ = gz.Close() }()
reader = gz
case "br":
reader = brotli.NewReader(resp.Body)

View File

@@ -49,7 +49,7 @@ func (c *Client) postJSONWithStatus(ctx context.Context, doer trans.Doer, url st
return nil, 0, err
}
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
payloadBytes, err := readResponseBody(resp)
if err != nil {
return nil, resp.StatusCode, err
@@ -86,7 +86,7 @@ func (c *Client) getJSONWithStatus(ctx context.Context, doer trans.Doer, url str
return nil, 0, err
}
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
payloadBytes, err := readResponseBody(resp)
if err != nil {
return nil, resp.StatusCode, err

View File

@@ -5,6 +5,7 @@ import (
"strings"
)
//nolint:unused // retained for raw-sample processing entrypoints.
func extractProcessedVisibleText(raw []byte, kind, contentType string) string {
if len(raw) == 0 {
return ""
@@ -22,6 +23,7 @@ func extractProcessedVisibleText(raw []byte, kind, contentType string) string {
return parseOpenAIStreamText(string(raw))
}
//nolint:unused // retained for raw-sample processing entrypoints.
func parseOpenAIStreamText(raw string) string {
if strings.TrimSpace(raw) == "" {
return ""
@@ -54,6 +56,7 @@ func parseOpenAIStreamText(raw string) string {
return out.String()
}
//nolint:unused // retained for raw-sample processing entrypoints.
func parseOpenAIJSONText(raw string) string {
if strings.TrimSpace(raw) == "" {
return ""
@@ -65,6 +68,7 @@ func parseOpenAIJSONText(raw string) string {
return extractOpenAIVisibleTextValue(decoded)
}
//nolint:unused // retained for raw-sample processing entrypoints.
func extractOpenAIVisibleTextValue(v any) string {
switch x := v.(type) {
case nil:

View File

@@ -24,7 +24,7 @@ type CollectResult struct {
// The caller is responsible for closing resp.Body unless closeBody is true.
func CollectStream(resp *http.Response, thinkingEnabled bool, closeBody bool) CollectResult {
if closeBody {
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
}
text := strings.Builder{}
thinking := strings.Builder{}

View File

@@ -57,9 +57,7 @@ func isFragmentStatusPath(path string) bool {
if mid == "" {
return false
}
if strings.HasPrefix(mid, "-") {
mid = mid[1:]
}
mid = strings.TrimPrefix(mid, "-")
if mid == "" {
return false
}
@@ -310,11 +308,12 @@ func extractContentRecursive(items []any, defaultType string) ([]ContentPart, bo
}
typeName, _ := x["type"].(string)
typeName = strings.ToUpper(typeName)
if typeName == "THINK" || typeName == "THINKING" {
switch typeName {
case "THINK", "THINKING":
parts = append(parts, ContentPart{Text: ct, Type: "thinking"})
} else if typeName == "RESPONSE" {
case "RESPONSE":
parts = append(parts, ContentPart{Text: ct, Type: "text"})
} else {
default:
parts = append(parts, ContentPart{Text: ct, Type: partType})
}
case string:

View File

@@ -103,7 +103,7 @@ func TestStartParsedLinePumpContextCancellation(t *testing.T) {
// Cancel context - this will cause the pump to exit on next send
cancel()
// Close the pipe to unblock scanner.Scan()
pw.Close()
_ = pw.Close()
// Drain remaining results
for range results {

View File

@@ -170,7 +170,7 @@ func (r *Runner) caseToolcallStreamMixed(ctx context.Context, cc *caseContext) e
cc.assert("tool_calls_delta_present", hasTool, "tool_calls delta missing")
cc.assert("no_raw_tool_json_leak", !rawLeak, "raw tool_calls leaked")
cc.assert("done_terminated", done, "expected [DONE]")
if !(hasTool && hasText) {
if !hasTool || !hasText {
r.warnings = append(r.warnings, "toolcall mixed stream did not produce both text and tool_calls in this run (model-side behavior dependent)")
}
return nil

View File

@@ -58,7 +58,7 @@ func (cc *caseContext) abortStreamRequest(ctx context.Context, spec requestSpec)
})
return err
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
buf := make([]byte, 512)
_, _ = resp.Body.Read(buf)
_ = resp.Body.Close()

View File

@@ -66,7 +66,7 @@ func (r *Runner) pruneOldRuns() error {
if err := os.RemoveAll(dirPath); err != nil {
errs = append(errs, fmt.Sprintf("remove %s: %v", name, err))
} else {
fmt.Fprintf(os.Stdout, "pruned old test run: %s\n", name)
_, _ = fmt.Fprintf(os.Stdout, "pruned old test run: %s\n", name)
}
}
@@ -82,7 +82,7 @@ func (r *Runner) runPreflight(ctx context.Context) error {
if err != nil {
return err
}
defer f.Close()
defer func() { _ = f.Close() }()
for _, step := range steps {
if _, err := fmt.Fprintf(f, "\n$ %s\n", strings.Join(step, " ")); err != nil {
return err
@@ -218,7 +218,7 @@ func (r *Runner) ping(path string) error {
if err != nil {
return err
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("status=%d", resp.StatusCode)
}

View File

@@ -115,7 +115,7 @@ func (cc *caseContext) requestOnce(ctx context.Context, spec requestSpec, attemp
cc.mu.Unlock()
return nil, err
}
defer resp.Body.Close()
defer func() { _ = resp.Body.Close() }()
body, _ := io.ReadAll(resp.Body)
cc.mu.Lock()
@@ -131,7 +131,7 @@ func (cc *caseContext) requestOnce(ctx context.Context, spec requestSpec, attemp
})
if spec.Stream {
cc.streamRaw.WriteString(fmt.Sprintf("### trace=%s url=%s\n", traceID, fullURL))
_, _ = fmt.Fprintf(&cc.streamRaw, "### trace=%s url=%s\n", traceID, fullURL)
cc.streamRaw.Write(body)
cc.streamRaw.WriteString("\n\n")
}

View File

@@ -126,7 +126,7 @@ func findFreePort() (int, error) {
if err != nil {
return 0, err
}
defer ln.Close()
defer func() { _ = ln.Close() }()
addr, ok := ln.Addr().(*net.TCPAddr)
if !ok {
return 0, errors.New("failed to detect tcp port")

View File

@@ -8,6 +8,8 @@ import (
var toolCallPattern = regexp.MustCompile(`\{\s*["']tool_calls["']\s*:\s*\[(.*?)\]\s*\}`)
var fencedJSONPattern = regexp.MustCompile("(?s)```(?:json)?\\s*(.*?)\\s*```")
var fencedCodeBlockPattern = regexp.MustCompile("(?s)```[\\s\\S]*?```")
//nolint:unused // retained for future markup tool-call heuristics.
var markupToolSyntaxPattern = regexp.MustCompile(`(?i)<(?:(?:[a-z0-9_:-]+:)?(?:tool_call|function_call|invoke)\b|(?:[a-z0-9_:-]+:)?function_calls\b|(?:[a-z0-9_:-]+:)?tool_use\b)`)
func buildToolCallCandidates(text string) []string {
@@ -190,6 +192,7 @@ func shouldSkipToolCallParsingForCodeFenceExample(text string) bool {
return !looksLikeToolCallSyntax(stripped)
}
//nolint:unused // retained for future markup tool-call heuristics.
func looksLikeMarkupToolSyntax(text string) bool {
return markupToolSyntaxPattern.MatchString(text)
}

View File

@@ -27,7 +27,7 @@ func repairInvalidJSONBackslashes(s string) string {
isHex := true
for j := 1; j <= 4; j++ {
r := runes[i+1+j]
if !((r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F')) {
if (r < '0' || r > '9') && (r < 'a' || r > 'f') && (r < 'A' || r > 'F') {
isHex = false
break
}

View File

@@ -5,8 +5,10 @@ import (
"strings"
)
//nolint:unused // retained for policy-level tool-name matching compatibility.
var toolNameLoosePattern = regexp.MustCompile(`[^a-z0-9]+`)
//nolint:unused // retained for policy-level tool-name matching compatibility.
func resolveAllowedToolNameWithLooseMatch(name string, allowed map[string]struct{}, allowedCanonical map[string]string) string {
if _, ok := allowed[name]; ok {
return name

View File

@@ -164,6 +164,7 @@ func filterToolCallsDetailed(parsed []ParsedToolCall, availableToolNames []strin
return out, nil
}
//nolint:unused // retained for policy-level tool-name matching compatibility.
func resolveAllowedToolName(name string, allowed map[string]struct{}, allowedCanonical map[string]string) string {
return resolveAllowedToolNameWithLooseMatch(name, allowed, allowedCanonical)
}

View File

@@ -7,7 +7,7 @@ func isLikelyJSONToolPayloadCandidate(candidate string) bool {
if trimmed == "" {
return false
}
if !(strings.HasPrefix(trimmed, "{") || strings.HasPrefix(trimmed, "[")) {
if !strings.HasPrefix(trimmed, "{") && !strings.HasPrefix(trimmed, "[") {
return false
}
lower := strings.ToLower(trimmed)

View File

@@ -27,7 +27,7 @@ func repairInvalidJSONBackslashes(s string) string {
isHex := true
for j := 1; j <= 4; j++ {
r := runes[i+1+j]
if !((r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F')) {
if (r < '0' || r > '9') && (r < 'a' || r > 'f') && (r < 'A' || r > 'F') {
isHex = false
break
}