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
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/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
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
}