1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
//! Non-hot-path LLM dispatch via the user's installed agent CLI.
//!
//! DiffLore wants to make occasional LLM calls (candidate-name
//! extraction, `plan_pr` secondary judgment, future session-mining
//! gates) without requiring the user to configure a BYOK provider —
//! the cloud-spec measurement showed 85–95% of users have no LLM key
//! configured at install time, so a feature gated on "user has Anthropic
//! / OpenAI / Gemini key" misses most of the user base.
//!
//! The workaround DiffLore borrows from Activeloop hivemind's
//! `gate-runner.ts`: if the user is already running inside (or alongside)
//! an agent CLI — Claude Code, Codex, cursor-agent, Gemini CLI — that
//! CLI's binary is on PATH (or in a well-known install location). We
//! shell out to it for the gate call, pay nothing for the LLM key
//! (the CLI handles auth via the user's existing session), and stay
//! out of the hot path so the CLI's latency only affects best-effort
//! gates, never the user-typed-command latency.
//!
//! ## Surface
//!
//! One public async function:
//!
//! ```rust,ignore
//! use std::time::Duration;
//! use difflore_cli::agent_cli::{AgentKind, dispatch_gate};
//!
//! async fn example() {
//! let result = dispatch_gate(
//! AgentKind::ClaudeCode,
//! "Rate this PR description 1-5: ...",
//! Duration::from_secs(30),
//! ).await;
//! if !result.errored {
//! println!("model said: {}", result.stdout);
//! }
//! }
//! ```
//!
//! ## Non-goals
//!
//! - Not a streaming API. The use cases are short ratings / yes-no
//! gates / JSON envelopes — buffering the whole response is fine and
//! simpler than wiring a streaming reader.
//! - Not a BYOK provider replacement. The existing
//! `crate::commands::providers` flow stays the canonical path for
//! power users who want per-model latency / cost knobs. Gate-runner-
//! style CLI dispatch is the "no key required" fallback.
//! - Not Windsurf-aware: Windsurf ships no headless CLI today. The
//! enum still has a `Windsurf` variant for symmetry with the hook
//! adapters, but `dispatch_gate` returns an errored `GateResult`
//! immediately rather than try to spawn anything.
pub use ;
use Duration;
/// Dispatch a single LLM call to whichever agent CLI matches `agent`.
///
/// Returns a `GateResult` describing what the CLI printed and whether
/// it errored. Never panics — even on a missing binary, the result is
/// `{ errored: true, error_message: "...", stdout: "", stderr: "" }`
/// so callers can downgrade a failed gate to "skip, don't block".
///
/// `time_budget` is enforced via `tokio::time::timeout`; the child
/// process is killed on drop. Pick a budget that matches how
/// best-effort the gate is — DiffLore's typical gates run with 15-30s.
///
/// Side-effects: spawns a child process, inherits the parent's
/// environment, sends `prompt` as a single argv positional (the last
/// one) so prompt content that looks like a flag is dispatched as
/// argument data, not parsed by the CLI. If your prompt itself starts
/// with `-`, the dispatched CLI may misparse it — sanitise prompts
/// upstream of this call.
pub async