From 3fccec0e22241c153b9d9e87804c474c02dc3594 Mon Sep 17 00:00:00 2001 From: "CJACK." Date: Sun, 22 Mar 2026 10:24:11 +0800 Subject: [PATCH 1/3] test: remove unused asFloat helper --- .../adapter/openai/handler_toolcall_policy.go | 6 - .../openai/responses_stream_runtime_core.go | 10 - .../adapter/openai/responses_stream_test.go | 15 - .../adapter/openai/tool_sieve_incremental.go | 288 ------------------ .../adapter/openai/tool_sieve_jsonscan.go | 109 ------- internal/adapter/openai/tool_sieve_state.go | 11 - internal/deepseek/client_http_json.go | 11 - internal/util/toolcalls_candidates.go | 14 +- webui/src/components/AccountManager.jsx | 3 - webui/src/components/ApiTester.jsx | 3 - webui/src/components/Settings.jsx | 3 - webui/src/components/VercelSync.jsx | 3 - webui/src/layout/DashboardShell.jsx | 16 +- 13 files changed, 11 insertions(+), 481 deletions(-) delete mode 100644 internal/adapter/openai/tool_sieve_incremental.go delete mode 100644 webui/src/components/AccountManager.jsx delete mode 100644 webui/src/components/ApiTester.jsx delete mode 100644 webui/src/components/Settings.jsx delete mode 100644 webui/src/components/VercelSync.jsx diff --git a/internal/adapter/openai/handler_toolcall_policy.go b/internal/adapter/openai/handler_toolcall_policy.go index 9f0e839..7b6f39c 100644 --- a/internal/adapter/openai/handler_toolcall_policy.go +++ b/internal/adapter/openai/handler_toolcall_policy.go @@ -2,12 +2,6 @@ package openai import "strings" -func applyOpenAIChatPassThrough(req map[string]any, payload map[string]any) { - for k, v := range collectOpenAIChatPassThrough(req) { - payload[k] = v - } -} - func (h *Handler) toolcallFeatureMatchEnabled() bool { if h == nil || h.Store == nil { return true diff --git a/internal/adapter/openai/responses_stream_runtime_core.go b/internal/adapter/openai/responses_stream_runtime_core.go index c1ca926..727bae0 100644 --- a/internal/adapter/openai/responses_stream_runtime_core.go +++ b/internal/adapter/openai/responses_stream_runtime_core.go @@ -32,7 +32,6 @@ type responsesStreamRuntime struct { toolCallsDoneEmitted bool sieve toolStreamSieveState - thinkingSieve toolStreamSieveState thinking strings.Builder text strings.Builder visibleText strings.Builder @@ -169,15 +168,6 @@ func (s *responsesStreamRuntime) logToolPolicyRejections(textParsed util.ToolCal logRejected(textParsed, "text") } -func (s *responsesStreamRuntime) hasFunctionCallDone() bool { - for _, done := range s.functionDone { - if done { - return true - } - } - return false -} - func (s *responsesStreamRuntime) onParsed(parsed sse.LineResult) streamengine.ParsedDecision { if !parsed.Parsed { return streamengine.ParsedDecision{} diff --git a/internal/adapter/openai/responses_stream_test.go b/internal/adapter/openai/responses_stream_test.go index 7d15ede..02d1f4b 100644 --- a/internal/adapter/openai/responses_stream_test.go +++ b/internal/adapter/openai/responses_stream_test.go @@ -675,18 +675,3 @@ func extractAllSSEEventPayloads(body, targetEvent string) []map[string]any { } return out } - -func asFloat(v any) float64 { - switch x := v.(type) { - case float64: - return x - case float32: - return float64(x) - case int: - return float64(x) - case int64: - return float64(x) - default: - return 0 - } -} diff --git a/internal/adapter/openai/tool_sieve_incremental.go b/internal/adapter/openai/tool_sieve_incremental.go deleted file mode 100644 index d0d7842..0000000 --- a/internal/adapter/openai/tool_sieve_incremental.go +++ /dev/null @@ -1,288 +0,0 @@ -package openai - -import "strings" - -func buildIncrementalToolDeltas(state *toolStreamSieveState) []toolCallDelta { - if state.disableDeltas { - return nil - } - captured := state.capture.String() - if captured == "" { - return nil - } - lower := strings.ToLower(captured) - keyIdx := strings.Index(lower, "tool_calls") - if keyIdx < 0 { - return nil - } - start := strings.LastIndex(captured[:keyIdx], "{") - if start < 0 { - return nil - } - certainSingle, hasMultiple := classifyToolCallsIncrementalSafety(captured, keyIdx) - if hasMultiple { - state.disableDeltas = true - return nil - } - if !certainSingle { - // In uncertain phases (e.g. first call arrived but array not closed yet), - // avoid speculative deltas and wait for final parsed tool_calls payload. - return nil - } - callStart, ok := findFirstToolCallObjectStart(captured, keyIdx) - if !ok { - return nil - } - deltas := make([]toolCallDelta, 0, 2) - if state.toolName == "" { - name, ok := extractToolCallName(captured, callStart) - if !ok || name == "" { - return nil - } - state.toolName = name - } - if state.toolArgsStart < 0 { - argsStart, stringMode, ok := findToolCallArgsStart(captured, callStart) - if ok { - state.toolArgsString = stringMode - if stringMode { - state.toolArgsStart = argsStart + 1 - } else { - state.toolArgsStart = argsStart - } - state.toolArgsSent = state.toolArgsStart - } - } - if !state.toolNameSent { - if state.toolArgsStart < 0 { - return nil - } - state.toolNameSent = true - deltas = append(deltas, toolCallDelta{Index: 0, Name: state.toolName}) - } - if state.toolArgsStart < 0 || state.toolArgsDone { - return deltas - } - end, complete, ok := scanToolCallArgsProgress(captured, state.toolArgsStart, state.toolArgsString) - if !ok { - return deltas - } - if end > state.toolArgsSent { - deltas = append(deltas, toolCallDelta{ - Index: 0, - Arguments: captured[state.toolArgsSent:end], - }) - state.toolArgsSent = end - } - if complete { - state.toolArgsDone = true - } - return deltas -} - -func classifyToolCallsIncrementalSafety(text string, keyIdx int) (certainSingle bool, hasMultiple bool) { - arrStart, ok := findToolCallsArrayStart(text, keyIdx) - if !ok { - return false, false - } - i := skipSpaces(text, arrStart+1) - if i >= len(text) || text[i] != '{' { - return false, false - } - count := 0 - depth := 0 - quote := byte(0) - escaped := false - for ; i < len(text); i++ { - ch := text[i] - if quote != 0 { - if escaped { - escaped = false - continue - } - if ch == '\\' { - escaped = true - continue - } - if ch == quote { - quote = 0 - } - continue - } - if ch == '"' || ch == '\'' { - quote = ch - continue - } - if ch == '{' { - if depth == 0 { - count++ - if count > 1 { - return false, true - } - } - depth++ - continue - } - if ch == '}' { - if depth > 0 { - depth-- - } - continue - } - if ch == ',' && depth == 0 { - // top-level separator means at least one more tool call exists - // (or is expected). Treat as multi-call and stop incremental deltas. - return false, true - } - if ch == ']' && depth == 0 { - return count == 1, false - } - } - // array not closed yet: still uncertain whether more calls will appear - return false, false -} - -func findFirstToolCallObjectStart(text string, keyIdx int) (int, bool) { - arrStart, ok := findToolCallsArrayStart(text, keyIdx) - if !ok { - return -1, false - } - i := skipSpaces(text, arrStart+1) - if i >= len(text) || text[i] != '{' { - return -1, false - } - return i, true -} - -func findToolCallsArrayStart(text string, keyIdx int) (int, bool) { - i := keyIdx + len("tool_calls") - for i < len(text) && text[i] != ':' { - i++ - } - if i >= len(text) { - return -1, false - } - i = skipSpaces(text, i+1) - if i >= len(text) || text[i] != '[' { - return -1, false - } - return i, true -} - -func extractToolCallName(text string, callStart int) (string, bool) { - valueStart, ok := findObjectFieldValueStart(text, callStart, []string{"name"}) - if !ok || valueStart >= len(text) || text[valueStart] != '"' { - fnStart, fnOK := findFunctionObjectStart(text, callStart) - if !fnOK { - return "", false - } - valueStart, ok = findObjectFieldValueStart(text, fnStart, []string{"name"}) - if !ok || valueStart >= len(text) || text[valueStart] != '"' { - return "", false - } - } - name, _, ok := parseJSONStringLiteral(text, valueStart) - if !ok { - return "", false - } - return name, true -} - -func findToolCallArgsStart(text string, callStart int) (int, bool, bool) { - keys := []string{"input", "arguments", "args", "parameters", "params"} - valueStart, ok := findObjectFieldValueStart(text, callStart, keys) - if !ok { - fnStart, fnOK := findFunctionObjectStart(text, callStart) - if !fnOK { - return -1, false, false - } - valueStart, ok = findObjectFieldValueStart(text, fnStart, keys) - if !ok { - return -1, false, false - } - } - if valueStart >= len(text) { - return -1, false, false - } - ch := text[valueStart] - if ch == '{' || ch == '[' { - return valueStart, false, true - } - if ch == '"' { - return valueStart, true, true - } - return -1, false, false -} - -func scanToolCallArgsProgress(text string, start int, stringMode bool) (int, bool, bool) { - if start < 0 || start > len(text) { - return 0, false, false - } - if stringMode { - escaped := false - for i := start; i < len(text); i++ { - ch := text[i] - if escaped { - escaped = false - continue - } - if ch == '\\' { - escaped = true - continue - } - if ch == '"' { - return i, true, true - } - } - return len(text), false, true - } - if start >= len(text) { - return start, false, false - } - if text[start] != '{' && text[start] != '[' { - return 0, false, false - } - depth := 0 - quote := byte(0) - escaped := false - for i := start; i < len(text); i++ { - ch := text[i] - if quote != 0 { - if escaped { - escaped = false - continue - } - if ch == '\\' { - escaped = true - continue - } - if ch == quote { - quote = 0 - } - continue - } - if ch == '"' || ch == '\'' { - quote = ch - continue - } - if ch == '{' || ch == '[' { - depth++ - continue - } - if ch == '}' || ch == ']' { - depth-- - if depth == 0 { - return i + 1, true, true - } - } - } - return len(text), false, true -} - -func findFunctionObjectStart(text string, callStart int) (int, bool) { - valueStart, ok := findObjectFieldValueStart(text, callStart, []string{"function"}) - if !ok || valueStart >= len(text) || text[valueStart] != '{' { - return -1, false - } - return valueStart, true -} diff --git a/internal/adapter/openai/tool_sieve_jsonscan.go b/internal/adapter/openai/tool_sieve_jsonscan.go index d3abcc5..b49ef7a 100644 --- a/internal/adapter/openai/tool_sieve_jsonscan.go +++ b/internal/adapter/openai/tool_sieve_jsonscan.go @@ -1,7 +1,5 @@ package openai -import "strings" - func extractJSONObjectFrom(text string, start int) (string, int, bool) { if start < 0 || start >= len(text) || text[start] != '{' { return "", 0, false @@ -43,110 +41,3 @@ func extractJSONObjectFrom(text string, start int) (string, int, bool) { } return "", 0, false } - -func findObjectFieldValueStart(text string, objStart int, keys []string) (int, bool) { - if objStart < 0 || objStart >= len(text) || text[objStart] != '{' { - return 0, false - } - depth := 0 - quote := byte(0) - escaped := false - for i := objStart; i < len(text); i++ { - ch := text[i] - if quote != 0 { - if escaped { - escaped = false - continue - } - if ch == '\\' { - escaped = true - continue - } - if ch == quote { - quote = 0 - } - continue - } - if ch == '"' || ch == '\'' { - if depth == 1 { - key, end, ok := parseJSONStringLiteral(text, i) - if !ok { - return 0, false - } - j := skipSpaces(text, end) - if j >= len(text) || text[j] != ':' { - i = end - 1 - continue - } - j = skipSpaces(text, j+1) - if j >= len(text) { - return 0, false - } - if containsKey(keys, key) { - return j, true - } - i = j - 1 - continue - } - quote = ch - continue - } - if ch == '{' { - depth++ - continue - } - if ch == '}' { - depth-- - if depth == 0 { - break - } - } - } - return 0, false -} - -func parseJSONStringLiteral(text string, start int) (string, int, bool) { - if start < 0 || start >= len(text) || text[start] != '"' { - return "", 0, false - } - var b strings.Builder - escaped := false - for i := start + 1; i < len(text); i++ { - ch := text[i] - if escaped { - b.WriteByte(ch) - escaped = false - continue - } - if ch == '\\' { - escaped = true - continue - } - if ch == '"' { - return b.String(), i + 1, true - } - b.WriteByte(ch) - } - return "", 0, false -} - -func containsKey(keys []string, value string) bool { - for _, k := range keys { - if k == value { - return true - } - } - return false -} - -func skipSpaces(text string, i int) int { - for i < len(text) { - switch text[i] { - case ' ', '\t', '\n', '\r': - i++ - default: - return i - } - } - return i -} diff --git a/internal/adapter/openai/tool_sieve_state.go b/internal/adapter/openai/tool_sieve_state.go index 1db9413..f36560a 100644 --- a/internal/adapter/openai/tool_sieve_state.go +++ b/internal/adapter/openai/tool_sieve_state.go @@ -63,14 +63,3 @@ func appendTail(prev, next string, max int) string { } return combined[len(combined)-max:] } - -func looksLikeToolExampleContext(text string) bool { - return insideCodeFence(text) -} - -func insideCodeFence(text string) bool { - if text == "" { - return false - } - return strings.Count(text, "```")%2 == 1 -} diff --git a/internal/deepseek/client_http_json.go b/internal/deepseek/client_http_json.go index 1620b2e..a35d736 100644 --- a/internal/deepseek/client_http_json.go +++ b/internal/deepseek/client_http_json.go @@ -63,17 +63,6 @@ func (c *Client) postJSONWithStatus(ctx context.Context, doer trans.Doer, url st return out, resp.StatusCode, nil } -func (c *Client) getJSON(ctx context.Context, doer trans.Doer, url string, headers map[string]string) (map[string]any, error) { - body, status, err := c.getJSONWithStatus(ctx, doer, url, headers) - if err != nil { - return nil, err - } - if status == 0 { - return nil, errors.New("request failed") - } - return body, nil -} - func (c *Client) getJSONWithStatus(ctx context.Context, doer trans.Doer, url string, headers map[string]string) (map[string]any, int, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { diff --git a/internal/util/toolcalls_candidates.go b/internal/util/toolcalls_candidates.go index 49db011..e4495b7 100644 --- a/internal/util/toolcalls_candidates.go +++ b/internal/util/toolcalls_candidates.go @@ -7,7 +7,6 @@ import ( var toolCallPattern = regexp.MustCompile(`\{\s*["']tool_calls["']\s*:\s*\[(.*?)\]\s*\}`) var fencedJSONPattern = regexp.MustCompile("(?s)```(?:json)?\\s*(.*?)\\s*```") -var fencedBlockPattern = regexp.MustCompile("(?s)```.*?```") func buildToolCallCandidates(text string) []string { trimmed := strings.TrimSpace(text) @@ -82,12 +81,12 @@ func extractToolCallObjects(text string) []string { if searchLimit < offset { searchLimit = offset } - + start := strings.LastIndex(text[searchLimit:idx], "{") if start >= 0 { start += searchLimit } - + if start < 0 { offset = idx + len(matchedKeyword) continue @@ -113,7 +112,7 @@ func extractToolCallObjects(text string) []string { } break } - + if !foundObj { offset = idx + len(matchedKeyword) } @@ -174,10 +173,3 @@ func looksLikeToolExampleContext(text string) bool { } return strings.Contains(t, "```") } - -func stripFencedCodeBlocks(text string) string { - if strings.TrimSpace(text) == "" { - return "" - } - return fencedBlockPattern.ReplaceAllString(text, " ") -} diff --git a/webui/src/components/AccountManager.jsx b/webui/src/components/AccountManager.jsx deleted file mode 100644 index 2a37010..0000000 --- a/webui/src/components/AccountManager.jsx +++ /dev/null @@ -1,3 +0,0 @@ -import AccountManagerContainer from '../features/account/AccountManagerContainer' - -export default AccountManagerContainer diff --git a/webui/src/components/ApiTester.jsx b/webui/src/components/ApiTester.jsx deleted file mode 100644 index b688195..0000000 --- a/webui/src/components/ApiTester.jsx +++ /dev/null @@ -1,3 +0,0 @@ -import ApiTesterContainer from '../features/apiTester/ApiTesterContainer' - -export default ApiTesterContainer diff --git a/webui/src/components/Settings.jsx b/webui/src/components/Settings.jsx deleted file mode 100644 index 317374e..0000000 --- a/webui/src/components/Settings.jsx +++ /dev/null @@ -1,3 +0,0 @@ -import SettingsContainer from '../features/settings/SettingsContainer' - -export default SettingsContainer diff --git a/webui/src/components/VercelSync.jsx b/webui/src/components/VercelSync.jsx deleted file mode 100644 index 853b9e2..0000000 --- a/webui/src/components/VercelSync.jsx +++ /dev/null @@ -1,3 +0,0 @@ -import VercelSyncContainer from '../features/vercel/VercelSyncContainer' - -export default VercelSyncContainer diff --git a/webui/src/layout/DashboardShell.jsx b/webui/src/layout/DashboardShell.jsx index b0dc5ea..1a5d5c4 100644 --- a/webui/src/layout/DashboardShell.jsx +++ b/webui/src/layout/DashboardShell.jsx @@ -12,11 +12,11 @@ import { } from 'lucide-react' import clsx from 'clsx' -import AccountManager from '../components/AccountManager' -import ApiTester from '../components/ApiTester' +import AccountManagerContainer from '../features/account/AccountManagerContainer' +import ApiTesterContainer from '../features/apiTester/ApiTesterContainer' import BatchImport from '../components/BatchImport' -import VercelSync from '../components/VercelSync' -import Settings from '../components/Settings' +import VercelSyncContainer from '../features/vercel/VercelSyncContainer' +import SettingsContainer from '../features/settings/SettingsContainer' import LanguageToggle from '../components/LanguageToggle' import { useI18n } from '../i18n' @@ -73,15 +73,15 @@ export default function DashboardShell({ token, onLogout, config, fetchConfig, s const renderTab = () => { switch (activeTab) { case 'accounts': - return + return case 'test': - return + return case 'import': return case 'vercel': - return + return case 'settings': - return + return default: return null } From 5031ae0e6f971f5d820eceb0bdabdcbebd36f176 Mon Sep 17 00:00:00 2001 From: "CJACK." Date: Sun, 22 Mar 2026 10:38:08 +0800 Subject: [PATCH 2/3] ci: align refactor line gate with removed files --- plans/refactor-line-gate-targets.txt | 5 ----- tests/scripts/check-refactor-line-gate.sh | 6 +----- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/plans/refactor-line-gate-targets.txt b/plans/refactor-line-gate-targets.txt index ac45d57..c1ffd22 100644 --- a/plans/refactor-line-gate-targets.txt +++ b/plans/refactor-line-gate-targets.txt @@ -53,7 +53,6 @@ internal/adapter/openai/responses_stream_runtime_events.go internal/adapter/openai/responses_stream_runtime_toolcalls.go internal/adapter/openai/tool_sieve_state.go internal/adapter/openai/tool_sieve_core.go -internal/adapter/openai/tool_sieve_incremental.go internal/adapter/openai/tool_sieve_jsonscan.go internal/util/toolcalls_parse.go @@ -117,7 +116,6 @@ webui/src/app/useAdminAuth.js webui/src/app/useAdminConfig.js webui/src/layout/DashboardShell.jsx -webui/src/components/AccountManager.jsx webui/src/features/account/AccountManagerContainer.jsx webui/src/features/account/useAccountsData.js webui/src/features/account/useAccountActions.js @@ -127,14 +125,12 @@ webui/src/features/account/AccountsTable.jsx webui/src/features/account/AddKeyModal.jsx webui/src/features/account/AddAccountModal.jsx -webui/src/components/ApiTester.jsx webui/src/features/apiTester/ApiTesterContainer.jsx webui/src/features/apiTester/useApiTesterState.js webui/src/features/apiTester/useChatStreamClient.js webui/src/features/apiTester/ConfigPanel.jsx webui/src/features/apiTester/ChatPanel.jsx -webui/src/components/Settings.jsx webui/src/features/settings/SettingsContainer.jsx webui/src/features/settings/useSettingsForm.js webui/src/features/settings/settingsApi.js @@ -144,7 +140,6 @@ webui/src/features/settings/BehaviorSection.jsx webui/src/features/settings/ModelSection.jsx webui/src/features/settings/BackupSection.jsx -webui/src/components/VercelSync.jsx webui/src/features/vercel/VercelSyncContainer.jsx webui/src/features/vercel/useVercelSyncState.js webui/src/features/vercel/VercelSyncForm.jsx diff --git a/tests/scripts/check-refactor-line-gate.sh b/tests/scripts/check-refactor-line-gate.sh index 3fda714..4a48827 100755 --- a/tests/scripts/check-refactor-line-gate.sh +++ b/tests/scripts/check-refactor-line-gate.sh @@ -12,11 +12,7 @@ is_entry_file() { case "$1" in api/chat-stream.js|\ internal/js/helpers/stream-tool-sieve.js|\ - webui/src/App.jsx|\ - webui/src/components/AccountManager.jsx|\ - webui/src/components/ApiTester.jsx|\ - webui/src/components/Settings.jsx|\ - webui/src/components/VercelSync.jsx) + webui/src/App.jsx) return 0 ;; esac From 455489ffebe9906fc6880b2d6857a860217ea631 Mon Sep 17 00:00:00 2001 From: "CJACK." Date: Sun, 22 Mar 2026 10:38:18 +0800 Subject: [PATCH 3/3] ci: upgrade GitHub Actions Node runtime to 24 --- .github/workflows/quality-gates.yml | 2 +- .github/workflows/release-artifacts.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/quality-gates.yml b/.github/workflows/quality-gates.yml index 3d7c9a1..8f1d865 100644 --- a/.github/workflows/quality-gates.yml +++ b/.github/workflows/quality-gates.yml @@ -24,7 +24,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "24" cache: "npm" cache-dependency-path: webui/package-lock.json diff --git a/.github/workflows/release-artifacts.yml b/.github/workflows/release-artifacts.yml index d2ba851..133c509 100644 --- a/.github/workflows/release-artifacts.yml +++ b/.github/workflows/release-artifacts.yml @@ -32,7 +32,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "24" cache: "npm" cache-dependency-path: webui/package-lock.json