- add console_events.go for edge console event handling - add codex_exec.go for CLI codex execution - add codex_exec_test.go for persistent CLI tests - update console.go and oneshot.go with new features - update config and edge.yaml for new settings - update README files for edge and node apps
120 lines
3.4 KiB
Go
120 lines
3.4 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"iop/apps/node/internal/runtime"
|
|
"iop/packages/config"
|
|
)
|
|
|
|
var (
|
|
codexSessionIDPattern = regexp.MustCompile(`(?mi)^session id:\s*(\S+)\s*$`)
|
|
codexThreadStartedRegex = regexp.MustCompile(`\{[^{}]*"type"\s*:\s*"thread\.started"[^{}]*\}`)
|
|
)
|
|
|
|
func isCodexExecProfile(profile config.CLIProfileConf) bool {
|
|
return filepath.Base(profile.Command) == "codex" && len(profile.Args) > 0 && profile.Args[0] == "exec"
|
|
}
|
|
|
|
func (c *CLI) executeCodexExec(ctx context.Context, spec runtime.ExecutionSpec, profile config.CLIProfileConf, sink runtime.EventSink) error {
|
|
sess, err := c.resolveCodexExecSession(spec)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sess.mu.Lock()
|
|
defer sess.mu.Unlock()
|
|
|
|
prompt := extractPrompt(spec.Input)
|
|
args := codexExecArgs(profile.Args, sess.externalID, prompt)
|
|
output, err := c.executeCommand(ctx, spec, profile, args, prompt, sink)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if externalID := parseCodexSessionID(output); externalID != "" {
|
|
sess.externalID = externalID
|
|
}
|
|
if sess.externalID == "" {
|
|
return fmt.Errorf("cli adapter: codex exec did not report a session id")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *CLI) resolveCodexExecSession(spec runtime.ExecutionSpec) (*codexExecSession, error) {
|
|
key := sessionKey{model: spec.Model, sessionID: normalizeSessionID(spec.SessionID)}
|
|
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
if sess, ok := c.codexSessions[key]; ok {
|
|
return sess, nil
|
|
}
|
|
if spec.SessionMode == runtime.SessionModeRequireExisting {
|
|
return nil, fmt.Errorf("cli adapter: no persistent session for profile %q session %q", spec.Model, key.sessionID)
|
|
}
|
|
sess := &codexExecSession{key: key}
|
|
c.codexSessions[key] = sess
|
|
return sess, nil
|
|
}
|
|
|
|
func codexExecArgs(configured []string, externalSessionID, prompt string) []string {
|
|
if externalSessionID == "" {
|
|
args := append([]string{}, configured...)
|
|
return append(args, prompt)
|
|
}
|
|
|
|
args := []string{"exec", "resume"}
|
|
args = append(args, codexResumeOptions(configured)...)
|
|
args = append(args, externalSessionID, prompt)
|
|
return args
|
|
}
|
|
|
|
func codexResumeOptions(configured []string) []string {
|
|
if len(configured) > 0 && configured[0] == "exec" {
|
|
configured = configured[1:]
|
|
}
|
|
|
|
out := make([]string, 0, len(configured))
|
|
for i := 0; i < len(configured); i++ {
|
|
arg := configured[i]
|
|
switch {
|
|
case arg == "--color" || arg == "-C" || arg == "--cd" || arg == "-s" || arg == "--sandbox" ||
|
|
arg == "--profile" || arg == "-p" || arg == "--local-provider" || arg == "--add-dir" ||
|
|
arg == "--output-schema":
|
|
if i+1 < len(configured) {
|
|
i++
|
|
}
|
|
continue
|
|
case strings.HasPrefix(arg, "--color=") || strings.HasPrefix(arg, "--cd=") ||
|
|
strings.HasPrefix(arg, "--sandbox=") || strings.HasPrefix(arg, "--profile=") ||
|
|
strings.HasPrefix(arg, "--local-provider=") || strings.HasPrefix(arg, "--add-dir=") ||
|
|
strings.HasPrefix(arg, "--output-schema="):
|
|
continue
|
|
default:
|
|
out = append(out, arg)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func parseCodexSessionID(output string) string {
|
|
if matches := codexSessionIDPattern.FindStringSubmatch(output); len(matches) >= 2 {
|
|
return matches[1]
|
|
}
|
|
for _, raw := range codexThreadStartedRegex.FindAllString(output, -1) {
|
|
var ev struct {
|
|
Type string `json:"type"`
|
|
ThreadID string `json:"thread_id"`
|
|
}
|
|
if err := json.Unmarshal([]byte(raw), &ev); err == nil && ev.Type == "thread.started" && ev.ThreadID != "" {
|
|
return ev.ThreadID
|
|
}
|
|
}
|
|
return ""
|
|
}
|