Konzola i operater chat sada koriste pseudo-terminal (PTY) umesto pipe-a. Claude CLI detektuje terminal i šalje output odmah umesto da bufferuje. ANSI escape sekvence se uklanjaju pre slanja kroz SSE. Novi fajl: pty.go (startPTY, readPTY, stripAnsi) Biblioteka: github.com/creack/pty v1.1.24 5 novih testova za PTY funkcionalnost. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
72 lines
1.6 KiB
Go
72 lines
1.6 KiB
Go
package server
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"regexp"
|
|
|
|
"github.com/creack/pty"
|
|
)
|
|
|
|
// ansiRegex matches ANSI escape sequences for stripping terminal formatting.
|
|
var ansiRegex = regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]|\x1b\].*?\x07|\x1b\[[\?0-9;]*[a-zA-Z]`)
|
|
|
|
// stripAnsi removes ANSI escape codes from a string.
|
|
func stripAnsi(s string) string {
|
|
return ansiRegex.ReplaceAllString(s, "")
|
|
}
|
|
|
|
// startPTY starts a command in a pseudo-terminal and returns the PTY master fd.
|
|
// The caller is responsible for closing the returned *os.File.
|
|
func startPTY(cmd *exec.Cmd) (*os.File, error) {
|
|
ptmx, err := pty.StartWithSize(cmd, &pty.Winsize{Rows: 40, Cols: 120})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return ptmx, nil
|
|
}
|
|
|
|
// readPTY reads from a PTY master and calls sendLine for each chunk of text.
|
|
// It splits on newlines so each SSE event is one line.
|
|
func readPTY(ptmx io.Reader, sendLine func(string)) {
|
|
buf := make([]byte, 4096)
|
|
var partial string
|
|
for {
|
|
n, err := ptmx.Read(buf)
|
|
if n > 0 {
|
|
text := partial + stripAnsi(string(buf[:n]))
|
|
partial = ""
|
|
|
|
// Split into lines, keep partial for next read
|
|
for {
|
|
idx := -1
|
|
for i, b := range []byte(text) {
|
|
if b == '\n' || b == '\r' {
|
|
idx = i
|
|
break
|
|
}
|
|
}
|
|
if idx < 0 {
|
|
partial = text
|
|
break
|
|
}
|
|
line := text[:idx]
|
|
// Skip empty lines from \r\n sequences
|
|
if line != "" {
|
|
sendLine(line)
|
|
}
|
|
// Skip past the newline character(s)
|
|
text = text[idx+1:]
|
|
}
|
|
}
|
|
if err != nil {
|
|
// Send remaining partial text
|
|
if partial != "" {
|
|
sendLine(partial)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|