fix(coder-lite,openagents-cli): make --dev a mode, not a lane

341e7f9838d0 · AtlantisPleb · · parent 3b16e0679b4c

fix(coder-lite,openagents-cli): make --dev a mode, not a lane

OpenResponses is the transport, not the model/lane. Revert Lane from
adding an OpenResponses variant and instead add a use_openresponses flag
on CoderRuntimeSession. --dev sets that flag while preserving the lane
the user named, so the status bar shows Coder Auto/Flash/Pro/etc. again
and the turn is sent to POST /api/v1/responses with streaming.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By
Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • modified crates/coder-lite/src/interactive.rs
  • modified crates/coder-lite/src/main.rs
  • modified crates/coder-lite/src/runtime.rs
  • modified crates/coder-lite/tests/turn.rs
  • modified crates/openagents-cli/src/runtime.rs

Diff

5 files changed, +33 -41

crates/coder-lite/src/interactive.rs modified +4

@@ -52,6 +52,8 @@ pub struct SessionOptions {

52 52
    /// `--reasoning`, recorded on the thread at open. `None` leaves the
53 53
    /// deployment's own default, which is a different answer from naming one.
54 54
    pub reasoning: Option<String>,
55
    /// `--dev` routes to the OpenResponses streaming surface.
56
    pub dev: bool,
55 57
}
56 58
57 59
pub async fn run_tui(options: SessionOptions) -> Result<(), Box<dyn std::error::Error>> {

@@ -99,6 +101,7 @@ pub async fn run_tui(options: SessionOptions) -> Result<(), Box<dyn std::error::

99 101
                &options.lane_name,
100 102
                options.reasoning.clone(),
101 103
                agents.clone(),
104
                options.dev,
102 105
                tx.clone(),
103 106
            ))));
104 107
            ui.entries.push(Entry::new(

@@ -196,6 +199,7 @@ pub async fn run_tui(options: SessionOptions) -> Result<(), Box<dyn std::error::

196 199
                                &options.lane_name,
197 200
                                options.reasoning.clone(),
198 201
                                agents.clone(),
202
                                options.dev,
199 203
                                tx.clone(),
200 204
                            ))));
201 205
                            ui.entries.push(Entry::new(
crates/coder-lite/src/main.rs modified +5 -10

@@ -40,10 +40,9 @@ Options:

40 40
                     that already is.
41 41
  --lane <name>      Which model answers. `auto` leaves it to the deployment;
42 42
                     `flash` and `pro` are tiers; `local` or `ollama:<model>`
43
                     answers from this machine; `openresponses` or
44
                     `openresponses:<model>` uses the OpenResponses surface;
45
                     any other name is checked against GET /api/v1/models and
46
                     refused if it is not served. Defaults to `auto`.
43
                     answers from this machine; any other name is checked
44
                     against GET /api/v1/models and refused if it is not
45
                     served. Defaults to `auto`.
47 46
  --reasoning <how>  Recorded on the thread as its reasoning effort. Omit to
48 47
                     leave the deployment's own default.
49 48
  -h, --help         Print this and exit.

@@ -78,13 +77,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {

78 77
                    env::set_var("OPENAGENTS_API_URL", DEV_API_URL);
79 78
                    env::set_var("OPENAGENTS_BASE_URL", DEV_BASE_URL);
80 79
                }
81
                // --dev routes through the OpenResponses streaming surface.
82
                options.lane_name = if options.lane_name == "auto" {
83
                    "openresponses".to_string()
84
                } else {
85
                    format!("openresponses:{}", options.lane_name)
86
                };
87 80
            }
81
            options.dev = dev;
88 82
            options
89 83
        }
90 84
        Ok(Parsed::Said) => return Ok(()),

@@ -112,6 +106,7 @@ fn parse(arguments: &[String]) -> Result<Parsed, String> {

112 106
    let mut options = SessionOptions {
113 107
        lane_name: "auto".to_string(),
114 108
        reasoning: None,
109
        dev: false,
115 110
    };
116 111
    let mut dev = false;
117 112
    let mut index = 0;
crates/coder-lite/src/runtime.rs modified +5 -1

@@ -171,6 +171,7 @@ impl Session {

171 171
        lane_name: &str,
172 172
        reasoning: Option<String>,
173 173
        agents: Vec<crate::acp::Agent>,
174
        dev: bool,
174 175
        tx: Sender<Control>,
175 176
    ) -> Self {
176 177
        Self::open_at(

@@ -180,6 +181,7 @@ impl Session {

180 181
            agents,
181 182
            api_base(),
182 183
            user_token(),
184
            dev,
183 185
            tx,
184 186
        )
185 187
    }

@@ -198,6 +200,7 @@ impl Session {

198 200
        agents: Vec<crate::acp::Agent>,
199 201
        api_base: String,
200 202
        token: Option<String>,
203
        dev: bool,
201 204
        tx: Sender<Control>,
202 205
    ) -> Self {
203 206
        let mut tools = HarnessToolRegistry::with_delegation(

@@ -270,7 +273,8 @@ impl Session {

270 273
                );
271 274
                send(&observed, Control::ToolDone { call_id, is_error });
272 275
            }
273
        }));
276
        }))
277
        .use_openresponses(dev);
274 278
        inner.reasoning = reasoning;
275 279
        inner.repository = repository();
276 280
crates/coder-lite/tests/turn.rs modified +1

@@ -178,6 +178,7 @@ fn session(base: &str, tx: Sender<Control>) -> Session {

178 178
        Vec::new(),
179 179
        base.to_string(),
180 180
        Some("oat_test".to_string()),
181
        false,
181 182
        tx,
182 183
    )
183 184
}
crates/openagents-cli/src/runtime.rs modified +18 -30

@@ -189,10 +189,6 @@ pub enum Lane {

189 189
    Pro,
190 190
    /// A model id named directly, checked against `GET /api/v1/models`.
191 191
    Named(String),
192
    /// The OpenResponses surface (`POST /api/v1/responses`), optionally pinned
193
    /// to a model id. No thread is opened; the whole conversation is sent each
194
    /// turn and the response streams as server-sent events.
195
    OpenResponses(Option<String>),
196 192
    /// Ollama on this machine. An empty string means "whatever is installed".
197 193
    Local(String),
198 194
}

@@ -203,25 +199,16 @@ impl Lane {

203 199
    /// settles it and the refusal can name what this deployment serves.
204 200
    #[allow(clippy::should_implement_trait)]
205 201
    pub fn from_str(s: &str) -> Self {
206
        let lower = s.trim().to_lowercase();
207
        match lower.as_str() {
202
        match s.trim().to_lowercase().as_str() {
208 203
            "" | "auto" => Lane::Auto,
209 204
            "ox-alpha" | "ox" | "openagents" => Lane::OxAlpha,
210 205
            "flash" | "coder-flash" | "gemini" | "gemini-flash" | "gemini-3.7-flash" => Lane::Flash,
211 206
            "pro" | "coder-pro" | "gpt-5.6-luna" | "luna" => Lane::Pro,
212
            "openresponses" | "dev" => Lane::OpenResponses(None),
213 207
            "local" | "ollama" => Lane::Local(String::new()),
214
            other => {
215
                if let Some(tail) = other.strip_prefix("ollama:") {
216
                    Lane::Local(tail.trim().to_string())
217
                } else if let Some(tail) = other.strip_prefix("openresponses:") {
218
                    Lane::OpenResponses(Some(tail.trim().to_string()))
219
                } else if let Some(tail) = other.strip_prefix("dev:") {
220
                    Lane::OpenResponses(Some(tail.trim().to_string()))
221
                } else {
222
                    Lane::Named(other.to_string())
223
                }
208
            other if other.starts_with("ollama:") => {
209
                Lane::Local(other.trim_start_matches("ollama:").trim().to_string())
224 210
            }
211
            other => Lane::Named(other.to_string()),
225 212
        }
226 213
    }
227 214

@@ -233,8 +220,6 @@ impl Lane {

233 220
            Lane::Flash => Some(TIERS[0].1),
234 221
            Lane::Pro => Some(TIERS[1].1),
235 222
            Lane::Named(id) => Some(id.as_str()),
236
            Lane::OpenResponses(Some(id)) => Some(id.as_str()),
237
            Lane::OpenResponses(None) => None,
238 223
            // The local lane names its model to Ollama, never to the server.
239 224
            Lane::Local(_) => None,
240 225
        }

@@ -245,11 +230,6 @@ impl Lane {

245 230
        matches!(self, Lane::Local(_))
246 231
    }
247 232
248
    /// Whether this lane talks to the OpenResponses streaming surface.
249
    pub fn is_openresponses(&self) -> bool {
250
        matches!(self, Lane::OpenResponses(_))
251
    }
252
253 233
    /// The tier this lane belongs to: `auto`, `flash`, `pro`, or `local`.
254 234
    ///
255 235
    /// A model id no tier pins has no tier, which is a different answer from

@@ -261,7 +241,6 @@ impl Lane {

261 241
            Lane::Flash => Some("flash"),
262 242
            Lane::Pro => Some("pro"),
263 243
            Lane::Local(_) => Some("local"),
264
            Lane::OpenResponses(_) => Some("openresponses"),
265 244
            Lane::OxAlpha | Lane::Named(_) => None,
266 245
        }
267 246
    }

@@ -274,8 +253,6 @@ impl Lane {

274 253
            Lane::Pro => "Coder Pro".to_string(),
275 254
            Lane::Local(model) if model.is_empty() => "Coder Local".to_string(),
276 255
            Lane::Local(model) => format!("Coder Local ({model})"),
277
            Lane::OpenResponses(None) => "Coder OpenResponses".to_string(),
278
            Lane::OpenResponses(Some(model)) => format!("Coder OpenResponses ({model})"),
279 256
            Lane::OxAlpha => "Coder (ox-alpha)".to_string(),
280 257
            Lane::Named(id) => format!("Coder ({id})"),
281 258
        }

@@ -587,6 +564,10 @@ pub struct ChatMessage {

587 564
588 565
pub struct CoderRuntimeSession {
589 566
    pub lane: Lane,
567
    /// Whether turns talk to the OpenResponses streaming surface instead of
568
    /// opening a thread. `Lane` still names the model; this flag only picks the
569
    /// transport.
570
    pub use_openresponses: bool,
590 571
    /// The grant this session opened, reused across its turns.
591 572
    ///
592 573
    /// `None` on the local lane, always: there is no grant to hold, and

@@ -675,6 +656,7 @@ impl CoderRuntimeSession {

675 656
    ) -> Self {
676 657
        Self {
677 658
            lane,
659
            use_openresponses: false,
678 660
            last_grant: None,
679 661
            last_model: None,
680 662
            last_usage: TurnUsage::default(),

@@ -719,6 +701,12 @@ impl CoderRuntimeSession {

719 701
        self
720 702
    }
721 703
704
    /// Use the OpenResponses streaming surface for this session's turns.
705
    pub fn use_openresponses(mut self, yes: bool) -> Self {
706
        self.use_openresponses = yes;
707
        self
708
    }
709
722 710
    fn tell(&self, event: ToolEvent) {
723 711
        if let Some(observer) = &self.tool_observer {
724 712
            observer(event);

@@ -1277,10 +1265,10 @@ impl CoderRuntimeSession {

1277 1265
             reported an outcome.",
1278 1266
        ));
1279 1267
1280
        let answered = if self.lane.is_local() {
1281
            self.run_local_turn(&tool_defs, chunk_callback).await
1282
        } else if self.lane.is_openresponses() {
1268
        let answered = if self.use_openresponses {
1283 1269
            self.run_responses_turn(&tool_defs, chunk_callback).await
1270
        } else if self.lane.is_local() {
1271
            self.run_local_turn(&tool_defs, chunk_callback).await
1284 1272
        } else {
1285 1273
            self.run_thread_turn(prompt, &tool_defs, chunk_callback)
1286 1274
                .await

This page updates live while a promote is in flight · changelog