Skip to content

Commit 22e5dca

Browse files
intel352claude
andcommitted
feat: SupportsInteractivePTY() flag per adapter
Only Claude Code uses interactive PTY (vt10x) for now — its screen layout is validated. Copilot, Codex, Gemini, Cursor use non-interactive exec or JSON streaming until their interactive PTY is refined. Stream() resolution order: 1. Interactive PTY (if SupportsInteractivePTY) — multi-turn 2. JSON streaming (if StreamingArgs) — structured events 3. Non-interactive exec — simplest fallback Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 3dd79b0 commit 22e5dca

File tree

3 files changed

+33
-10
lines changed

3 files changed

+33
-10
lines changed

genkit/pty_adapters.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ func (ClaudeCodeAdapter) ParseResponse(raw string) string {
8181
return parseResponseDefault(raw)
8282
}
8383

84+
func (ClaudeCodeAdapter) SupportsInteractivePTY() bool { return true }
85+
8486
func (ClaudeCodeAdapter) StreamingArgs(msg string) []string {
8587
return []string{"-p", msg, "--output-format", "stream-json", "--verbose"}
8688
}
@@ -299,12 +301,20 @@ func (CursorCLIAdapter) ParseResponse(raw string) string {
299301
// StreamingArgs/ParseStreamEvent for adapters without JSON streaming support.
300302
// They use the streamFallback path (non-interactive exec).
301303

304+
// Copilot uses non-interactive fallback for now — interactive PTY response
305+
// extraction needs refinement for Copilot's specific screen layout.
302306
func (CopilotCLIAdapter) StreamingArgs(_ string) []string { return nil }
303307
func (CopilotCLIAdapter) ParseStreamEvent(_ string) (string, bool) { return "", false }
304308

309+
// SupportsInteractivePTY returns false — Copilot should use non-interactive exec.
310+
func (CopilotCLIAdapter) SupportsInteractivePTY() bool { return false }
311+
312+
func (CodexCLIAdapter) SupportsInteractivePTY() bool { return false }
305313
func (CodexCLIAdapter) StreamingArgs(_ string) []string { return nil }
306314
func (CodexCLIAdapter) ParseStreamEvent(_ string) (string, bool) { return "", false }
307315

316+
func (GeminiCLIAdapter) SupportsInteractivePTY() bool { return false }
317+
308318
func (GeminiCLIAdapter) StreamingArgs(msg string) []string {
309319
return []string{"-p", msg, "--output-format", "stream-json"}
310320
}
@@ -328,5 +338,6 @@ func (GeminiCLIAdapter) ParseStreamEvent(line string) (string, bool) {
328338
return "", false
329339
}
330340

341+
func (CursorCLIAdapter) SupportsInteractivePTY() bool { return false }
331342
func (CursorCLIAdapter) StreamingArgs(_ string) []string { return nil }
332343
func (CursorCLIAdapter) ParseStreamEvent(_ string) (string, bool) { return "", false }

genkit/pty_provider.go

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ type CLIAdapter interface {
4040
// Returns the text content and true if the line contained assistant text,
4141
// or empty string and false if the line should be skipped.
4242
ParseStreamEvent(line string) (string, bool)
43+
// SupportsInteractivePTY returns true if the tool's interactive TUI is
44+
// compatible with vt10x screen reading. Tools that return false skip the
45+
// interactive path and go straight to JSON streaming or non-interactive exec.
46+
SupportsInteractivePTY() bool
4347
}
4448

4549
// ptyProvider implements provider.Provider by driving a CLI tool via PTY.
@@ -108,19 +112,26 @@ func (p *ptyProvider) Stream(ctx context.Context, messages []provider.Message, _
108112
go func() {
109113
defer close(ch)
110114

111-
// Primary: interactive PTY session via vt10x virtual terminal.
112-
if err := p.streamInteractive(ctx, msg, ch); err != nil {
113-
// If interactive fails, try JSON streaming as fallback.
114-
if args := p.adapter.StreamingArgs(msg); args != nil {
115-
if jsonErr := p.streamJSON(ctx, args, ch); jsonErr == nil {
116-
return
117-
}
115+
// Resolution order:
116+
// 1. Interactive PTY (if adapter supports it) — multi-turn session context
117+
// 2. JSON streaming (if adapter provides StreamingArgs) — structured events
118+
// 3. Non-interactive exec — simplest fallback
119+
120+
if p.adapter.SupportsInteractivePTY() {
121+
if err := p.streamInteractive(ctx, msg, ch); err == nil {
122+
return
118123
}
119-
// Last resort: non-interactive exec.
120-
if fallbackErr := p.streamFallback(ctx, msg, ch); fallbackErr != nil {
121-
ch <- provider.StreamEvent{Type: "error", Error: err.Error()}
124+
}
125+
126+
if args := p.adapter.StreamingArgs(msg); args != nil {
127+
if err := p.streamJSON(ctx, args, ch); err == nil {
128+
return
122129
}
123130
}
131+
132+
if err := p.streamFallback(ctx, msg, ch); err != nil {
133+
ch <- provider.StreamEvent{Type: "error", Error: err.Error()}
134+
}
124135
}()
125136

126137
return ch, nil

genkit/pty_provider_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ func (m *mockCLIAdapter) ParseResponse(raw string) string {
4949
return stripANSI(raw)
5050
}
5151

52+
func (m *mockCLIAdapter) SupportsInteractivePTY() bool { return false }
5253
func (m *mockCLIAdapter) StreamingArgs(_ string) []string { return nil }
5354
func (m *mockCLIAdapter) ParseStreamEvent(_ string) (string, bool) { return "", false }
5455

0 commit comments

Comments
 (0)