2026-03-30 14:09:15 +00:00
|
|
|
package winlimit
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"cursor-api-proxy/internal/env"
|
|
|
|
|
"runtime"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const WinPromptOmissionPrefix = "[Earlier messages omitted: Windows command-line length limit.]\n\n"
|
2026-04-01 00:53:34 +00:00
|
|
|
const LinuxPromptOmissionPrefix = "[Earlier messages omitted: Linux ARG_MAX command-line length limit.]\n\n"
|
|
|
|
|
|
|
|
|
|
// safeLinuxArgMax returns a conservative estimate of ARG_MAX on Linux.
|
|
|
|
|
// The actual limit is typically 2MB; we use 1.5MB to leave room for env vars.
|
|
|
|
|
func safeLinuxArgMax() int {
|
|
|
|
|
return 1536 * 1024
|
|
|
|
|
}
|
2026-03-30 14:09:15 +00:00
|
|
|
|
|
|
|
|
type FitPromptResult struct {
|
|
|
|
|
OK bool
|
|
|
|
|
Args []string
|
|
|
|
|
Truncated bool
|
|
|
|
|
OriginalLength int
|
|
|
|
|
FinalPromptLength int
|
|
|
|
|
Error string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func estimateCmdlineLength(resolved env.AgentCommand) int {
|
|
|
|
|
argv := append([]string{resolved.Command}, resolved.Args...)
|
|
|
|
|
if resolved.WindowsVerbatimArguments {
|
|
|
|
|
n := 0
|
|
|
|
|
for _, a := range argv {
|
|
|
|
|
n += len(a)
|
|
|
|
|
}
|
|
|
|
|
if len(argv) > 1 {
|
|
|
|
|
n += len(argv) - 1
|
|
|
|
|
}
|
|
|
|
|
return n + 512
|
|
|
|
|
}
|
|
|
|
|
dstLen := 0
|
|
|
|
|
for _, a := range argv {
|
|
|
|
|
dstLen += len(a)
|
|
|
|
|
}
|
|
|
|
|
dstLen = dstLen*2 + len(argv)*2
|
|
|
|
|
if len(argv) > 1 {
|
|
|
|
|
dstLen += len(argv) - 1
|
|
|
|
|
}
|
|
|
|
|
return dstLen + 512
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func FitPromptToWinCmdline(agentBin string, fixedArgs []string, prompt string, maxCmdline int, cwd string) FitPromptResult {
|
|
|
|
|
if runtime.GOOS != "windows" {
|
2026-04-01 00:53:34 +00:00
|
|
|
return fitPromptLinux(fixedArgs, prompt)
|
2026-03-30 14:09:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
e := env.OsEnvToMap()
|
|
|
|
|
measured := func(p string) int {
|
|
|
|
|
args := make([]string, len(fixedArgs)+1)
|
|
|
|
|
copy(args, fixedArgs)
|
|
|
|
|
args[len(fixedArgs)] = p
|
|
|
|
|
resolved := env.ResolveAgentCommand(agentBin, args, e, cwd)
|
|
|
|
|
return estimateCmdlineLength(resolved)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if measured("") > maxCmdline {
|
|
|
|
|
return FitPromptResult{
|
|
|
|
|
OK: false,
|
|
|
|
|
Error: "Windows command line exceeds the configured limit even without a prompt; shorten workspace path, model id, or CURSOR_BRIDGE_WIN_CMDLINE_MAX.",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if measured(prompt) <= maxCmdline {
|
|
|
|
|
args := make([]string, len(fixedArgs)+1)
|
|
|
|
|
copy(args, fixedArgs)
|
|
|
|
|
args[len(fixedArgs)] = prompt
|
|
|
|
|
return FitPromptResult{
|
|
|
|
|
OK: true,
|
|
|
|
|
Args: args,
|
|
|
|
|
Truncated: false,
|
|
|
|
|
OriginalLength: len(prompt),
|
|
|
|
|
FinalPromptLength: len(prompt),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prefix := WinPromptOmissionPrefix
|
|
|
|
|
if measured(prefix) > maxCmdline {
|
|
|
|
|
return FitPromptResult{
|
|
|
|
|
OK: false,
|
|
|
|
|
Error: "Windows command line too long to fit even the truncation notice; shorten workspace path or flags.",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lo, hi, best := 0, len(prompt), 0
|
|
|
|
|
for lo <= hi {
|
|
|
|
|
mid := (lo + hi) / 2
|
|
|
|
|
var tail string
|
|
|
|
|
if mid > 0 {
|
|
|
|
|
tail = prompt[len(prompt)-mid:]
|
|
|
|
|
}
|
|
|
|
|
candidate := prefix + tail
|
|
|
|
|
if measured(candidate) <= maxCmdline {
|
|
|
|
|
best = mid
|
|
|
|
|
lo = mid + 1
|
|
|
|
|
} else {
|
|
|
|
|
hi = mid - 1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var finalPrompt string
|
|
|
|
|
if best == 0 {
|
|
|
|
|
finalPrompt = prefix
|
|
|
|
|
} else {
|
|
|
|
|
finalPrompt = prefix + prompt[len(prompt)-best:]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
args := make([]string, len(fixedArgs)+1)
|
|
|
|
|
copy(args, fixedArgs)
|
|
|
|
|
args[len(fixedArgs)] = finalPrompt
|
|
|
|
|
return FitPromptResult{
|
|
|
|
|
OK: true,
|
|
|
|
|
Args: args,
|
|
|
|
|
Truncated: true,
|
|
|
|
|
OriginalLength: len(prompt),
|
|
|
|
|
FinalPromptLength: len(finalPrompt),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 00:53:34 +00:00
|
|
|
// fitPromptLinux handles Linux ARG_MAX truncation.
|
|
|
|
|
func fitPromptLinux(fixedArgs []string, prompt string) FitPromptResult {
|
|
|
|
|
argMax := safeLinuxArgMax()
|
|
|
|
|
|
|
|
|
|
// Estimate total cmdline size: sum of all fixed args + prompt + null terminators
|
|
|
|
|
fixedLen := 0
|
|
|
|
|
for _, a := range fixedArgs {
|
|
|
|
|
fixedLen += len(a) + 1
|
|
|
|
|
}
|
|
|
|
|
totalLen := fixedLen + len(prompt) + 1
|
|
|
|
|
|
|
|
|
|
if totalLen <= argMax {
|
|
|
|
|
args := make([]string, len(fixedArgs)+1)
|
|
|
|
|
copy(args, fixedArgs)
|
|
|
|
|
args[len(fixedArgs)] = prompt
|
|
|
|
|
return FitPromptResult{
|
|
|
|
|
OK: true,
|
|
|
|
|
Args: args,
|
|
|
|
|
Truncated: false,
|
|
|
|
|
OriginalLength: len(prompt),
|
|
|
|
|
FinalPromptLength: len(prompt),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Need to truncate: keep the tail of the prompt (most recent messages)
|
|
|
|
|
prefix := LinuxPromptOmissionPrefix
|
|
|
|
|
available := argMax - fixedLen - len(prefix) - 1
|
|
|
|
|
if available < 0 {
|
|
|
|
|
available = 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var finalPrompt string
|
|
|
|
|
if available <= 0 {
|
|
|
|
|
finalPrompt = prefix
|
|
|
|
|
} else if available >= len(prompt) {
|
|
|
|
|
finalPrompt = prefix + prompt
|
|
|
|
|
} else {
|
|
|
|
|
finalPrompt = prefix + prompt[len(prompt)-available:]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
args := make([]string, len(fixedArgs)+1)
|
|
|
|
|
copy(args, fixedArgs)
|
|
|
|
|
args[len(fixedArgs)] = finalPrompt
|
|
|
|
|
return FitPromptResult{
|
|
|
|
|
OK: true,
|
|
|
|
|
Args: args,
|
|
|
|
|
Truncated: true,
|
|
|
|
|
OriginalLength: len(prompt),
|
|
|
|
|
FinalPromptLength: len(finalPrompt),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 14:09:15 +00:00
|
|
|
func WarnPromptTruncated(originalLength, finalLength int) {
|
|
|
|
|
_ = originalLength
|
|
|
|
|
_ = finalLength
|
|
|
|
|
}
|