iop/apps/node/internal/adapters/cli/codex_exec.go
toki 86ceabadc3 기능: CLI 프로필에 mode와 resume_args 설정을 추가한다
codex-exec 모드를 명시적으로 지정하기 위해 CLIProfileConf에
mode와 resume_args 필드를 추가한다. 기존 isCodexExecProfile()의
명명 기반 판별을 삭제하고 설정 기반으로 전환한다.

주요 변경사항:
- CLIProfileConf에 mode, resume_args 필드 추가
- codex_exec.go에서 isCodexExecProfile(), codexResumeOptions() 삭제
- resume_args를 사용하여 resume 명령어 구성
- edge.yaml에 codex 프로필 mode와 resume_args 설정 추가
- codex-exec 모드 전용 테스트 케이스 추가
2026-05-04 22:20:53 +09:00

88 lines
2.4 KiB
Go

package cli
import (
"context"
"encoding/json"
"fmt"
"regexp"
"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 (c *CLI) executeCodexExec(ctx context.Context, spec runtime.ExecutionSpec, profile config.CLIProfileConf, sink runtime.EventSink) error {
if profile.ResumeArgs == nil {
return fmt.Errorf("cli adapter: codex-exec mode requires resume_args in profile %q", spec.Model)
}
sess, err := c.resolveCodexExecSession(spec)
if err != nil {
return err
}
sess.mu.Lock()
defer sess.mu.Unlock()
prompt := extractPrompt(spec.Input)
args := codexExecArgs(profile, 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) {
agent := cliAgentName(spec)
key := sessionKey{agent: agent, 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 agent %q session %q", agent, key.sessionID)
}
sess := &codexExecSession{key: key}
c.codexSessions[key] = sess
return sess, nil
}
func codexExecArgs(profile config.CLIProfileConf, externalSessionID, prompt string) []string {
if externalSessionID == "" {
args := append([]string{}, profile.Args...)
return append(args, prompt)
}
args := append([]string{}, profile.ResumeArgs...)
return append(args, externalSessionID, prompt)
}
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 ""
}