터미널 세션 core와 CLI mode executor 경계를 분리해 후속 remote terminal bridge가 재사용할 내부 실행 기반을 만든다. adapter 기본값과 vLLM experimental surface도 명시해 production 실행 경로가 mock fallback이나 미구현 실행으로 숨지 않게 한다.
155 lines
4.3 KiB
Go
155 lines
4.3 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"regexp"
|
|
|
|
"iop/apps/node/internal/runtime"
|
|
"iop/packages/go/config"
|
|
)
|
|
|
|
var antigravityConversationPatterns = []*regexp.Regexp{
|
|
regexp.MustCompile(`(?m)Created conversation (` + uuidPattern + `)`),
|
|
regexp.MustCompile(`(?m)Streaming conversation (` + uuidPattern + `)`),
|
|
regexp.MustCompile(`(?m)Print mode: conversation=(` + uuidPattern + `)`),
|
|
regexp.MustCompile(`(?m)conversationID="(` + uuidPattern + `)"`),
|
|
}
|
|
|
|
const uuidPattern = `[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}`
|
|
|
|
func (e *antigravityExecutor) Execute(ctx context.Context, spec runtime.ExecutionSpec, profile config.CLIProfileConf, sink runtime.EventSink) error {
|
|
if len(profile.ResumeArgs) == 0 {
|
|
return fmt.Errorf("cli adapter: antigravity-print mode requires resume_args in profile %q", spec.Target)
|
|
}
|
|
|
|
sess, err := e.resolveAntigravitySession(spec)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sess.mu.Lock()
|
|
defer sess.mu.Unlock()
|
|
|
|
logFile, cleanup, err := createAntigravityLogFile()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer cleanup()
|
|
|
|
prompt := extractPrompt(spec.Input)
|
|
args := antigravityPrintArgs(profile, sess.conversationID, logFile, prompt)
|
|
output, err := e.cli.executeCommand(ctx, spec, profile, args, prompt, sink)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
logOutput, readErr := os.ReadFile(logFile)
|
|
if readErr != nil {
|
|
return fmt.Errorf("cli adapter: read antigravity log: %w", readErr)
|
|
}
|
|
if conversationID := parseAntigravityConversationID(string(logOutput) + output); conversationID != "" {
|
|
sess.conversationID = conversationID
|
|
}
|
|
if sess.conversationID == "" {
|
|
return fmt.Errorf("cli adapter: antigravity-print did not report a conversation id")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (e *antigravityExecutor) resolveAntigravitySession(spec runtime.ExecutionSpec) (*antigravitySession, error) {
|
|
target := cliTargetName(spec)
|
|
key := sessionKey{target: target, sessionID: normalizeSessionID(spec.SessionID)}
|
|
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
|
|
if sess, ok := e.sessions[key]; ok {
|
|
return sess, nil
|
|
}
|
|
if spec.SessionMode == runtime.SessionModeRequireExisting {
|
|
return nil, fmt.Errorf("cli adapter: no antigravity conversation for target %q session %q", target, key.sessionID)
|
|
}
|
|
sess := &antigravitySession{key: key}
|
|
e.sessions[key] = sess
|
|
return sess, nil
|
|
}
|
|
|
|
func (e *antigravityExecutor) Sessions() []sessionListEntry {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
snaps := make([]sessionListEntry, 0, len(e.sessions))
|
|
for k := range e.sessions {
|
|
snaps = append(snaps, sessionListEntry{"antigravity-print", k.target, k.sessionID})
|
|
}
|
|
return snaps
|
|
}
|
|
|
|
func (e *antigravityExecutor) Terminate(ctx context.Context, target, sessionID string) (bool, error) {
|
|
key := sessionKey{target: target, sessionID: normalizeSessionID(sessionID)}
|
|
e.mu.Lock()
|
|
_, ok := e.sessions[key]
|
|
if ok {
|
|
delete(e.sessions, key)
|
|
}
|
|
e.mu.Unlock()
|
|
return ok, nil
|
|
}
|
|
|
|
func (e *antigravityExecutor) Stop(ctx context.Context) error {
|
|
e.mu.Lock()
|
|
e.sessions = make(map[sessionKey]*antigravitySession)
|
|
e.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
func antigravityPrintArgs(profile config.CLIProfileConf, conversationID, logFile, prompt string) []string {
|
|
args := []string{"--log-file", logFile}
|
|
if conversationID == "" {
|
|
args = append(args, profile.Args...)
|
|
} else {
|
|
args = append(args, removeAntigravityPrintFlag(profile.ResumeArgs)...)
|
|
args = append(args, conversationID)
|
|
args = append(args, "--print")
|
|
}
|
|
return append(args, prompt)
|
|
}
|
|
|
|
func removeAntigravityPrintFlag(args []string) []string {
|
|
filtered := make([]string, 0, len(args))
|
|
for _, arg := range args {
|
|
switch arg {
|
|
case "--print", "--prompt", "-p":
|
|
continue
|
|
default:
|
|
filtered = append(filtered, arg)
|
|
}
|
|
}
|
|
return filtered
|
|
}
|
|
|
|
func createAntigravityLogFile() (string, func(), error) {
|
|
f, err := os.CreateTemp("", "iop-antigravity-*.log")
|
|
if err != nil {
|
|
return "", nil, fmt.Errorf("cli adapter: create antigravity log file: %w", err)
|
|
}
|
|
path := f.Name()
|
|
if err := f.Close(); err != nil {
|
|
_ = os.Remove(path)
|
|
return "", nil, fmt.Errorf("cli adapter: close antigravity log file: %w", err)
|
|
}
|
|
return path, func() { _ = os.Remove(path) }, nil
|
|
}
|
|
|
|
func parseAntigravityConversationID(s string) string {
|
|
var id string
|
|
for _, re := range antigravityConversationPatterns {
|
|
for _, match := range re.FindAllStringSubmatch(s, -1) {
|
|
if len(match) >= 2 {
|
|
id = match[1]
|
|
}
|
|
}
|
|
}
|
|
return id
|
|
}
|