Files
codex/prs/bolinfest/study/PR-2371-study.md
2025-09-02 15:17:45 -07:00

5.8 KiB
Raw Blame History

DOs

  • Centralize tool-type remapping: keep “web_search” → “web_search_preview” logic inside the tool JSON builder, not at call sites.
fn create_tools_json_for_responses_api(
    tools: &[OpenAiTool],
    auth_mode: Option<AuthMode>,
) -> anyhow::Result<Vec<serde_json::Value>> {
    let mut out = build_tools_json(tools)?;
    if auth_mode == Some(AuthMode::ChatGPT) {
        for t in &mut out {
            if t.get("type").and_then(|v| v.as_str()) == Some("web_search") {
                t["type"] = serde_json::Value::String("web_search_preview".to_string());
            }
        }
    }
    Ok(out)
}
  • Match SSE events explicitly: add a dedicated arm for “response.output_item.added” and siblings; dont bury logic inside a grouped catchall.
match event.kind.as_str() {
    "response.output_item.added" => {
        if let Some(item) = event.item.as_ref() {
            if item.get("type").and_then(|v| v.as_str()) == Some("web_search_call") {
                let call_id = item.get("id").and_then(|v| v.as_str()).unwrap_or("").to_string();
                let query = item.get("query").and_then(|v| v.as_str()).map(|s| s.to_string());
                let _ = tx_event.send(Ok(ResponseEvent::WebSearchCallBegin { call_id, query })).await;
            }
        }
    }
    "response.output_item.done" => {
        if let Some(item) = event.item.as_ref() {
            if item.get("type").and_then(|v| v.as_str()) == Some("web_search_call") {
                let call_id = item.get("id").and_then(|v| v.as_str()).unwrap_or("").to_string();
                let _ = tx_event.send(Ok(ResponseEvent::WebSearchCallEnd { call_id })).await;
            }
        }
    }
    other => debug!(kind=%other, "sse event"),
}
  • Pair begin/end events: introduce a matching “end” for every “begin”, propagate call_id (and the real query) through to the UI.
#[derive(Debug)]
pub enum ResponseEvent {
    WebSearchCallBegin { call_id: String, query: Option<String> },
    WebSearchCallEnd { call_id: String },
}
match msg {
    EventMsg::WebSearchBegin(ev) => self.add_to_history(history_cell::new_web_search_call(ev.query)),
    EventMsg::WebSearchEnd(_) => self.flush_answer_stream_with_separator(),
    _ => {}
}
  • Keep config borrowing tight: avoid unnecessary clones of config fields; clone only when lifetimes demand it.
// Good
let shell_environment_policy = cfg.shell_environment_policy.into();

// Only clone when needed
let chatgpt_base_url = cfg.chatgpt_base_url.clone().unwrap_or_else(|| "...".to_string());
  • Use a builder/struct for many config flags: replace “boolean soup” with named fields for clarity and test readability.
struct ToolsConfigBuilder {
    sandbox_policy: SandboxPolicy,
    plan_tool: bool,
    apply_patch_tool_type: Option<ApplyPatchToolType>,
    web_search_request: bool,
    streamable_shell: bool,
}

impl ToolsConfigBuilder {
    fn build(self) -> ToolsConfig { /* ... */ }
}

let tools_cfg = ToolsConfigBuilder {
    sandbox_policy: SandboxPolicy::ReadOnly,
    plan_tool: true,
    apply_patch_tool_type: None,
    web_search_request: true,
    streamable_shell: false,
}.build();
  • Keep override parsing consistent across bins: use the same API in TUI and exec; avoid adhoc rewrapping.
let cli_kv_overrides = cli.config_overrides.parse_overrides()?;
  • Keep JSON schema test types stable: only switch to String when the schema struct requires it; otherwise keep &str literals to avoid churn.
// If the schema is Vec<&'static str>
required: vec!["string_property", "number_property"]

// If the schema is Vec<String>
required: vec!["string_property".to_string(), "number_property".to_string()]
  • Preserve helpful logging: keep a default debug branch to surface unexpected SSE kinds.
other => debug!(kind=%other, "sse event"),

DONTs

  • Dont scatter feature-specific remapping at call sites.
// Dont do this inside request assembly
if auth_mode == Some(AuthMode::ChatGPT) {
    for tool in &mut tools_json {
        if tool["type"] == "web_search" {
            tool["type"] = "web_search_preview".into();
        }
    }
}
  • Dont hide web_search detection inside a generic “ignored events” block; give it a clear match arm.
// Avoid: nested if inside a grouped branch
| "response.in_progress" | "response.output_item.added" | "response.output_text.done" => {
    if event.kind == "response.output_item.added" { /* ... */ }
}
  • Dont emit only “begin” without a matching “end”; the UI needs completion signals.
// Incomplete
EventMsg::WebSearchBegin(/* ... */);
// Missing: EventMsg::WebSearchEnd
  • Dont default query to placeholders when the SSE payload can provide it; plumb the real value.
// Avoid
let q = query.unwrap_or_else(|| "Searching Web...".to_string());
  • Dont introduce .clone() where a borrow or move suffices; it adds allocations and noise.
// Avoid
let shell_environment_policy = cfg.shell_environment_policy.clone().into();
  • Dont expand positional boolean parameters; prefer a builder with named fields.
// Avoid
ToolsConfig::new(policy, /*plan*/ true, /*apply_patch*/ false, /*web_search*/ true, /*stream*/ false);
  • Dont claim backward compatibility for new fields unless there is a real migration; avoid speculative aliases.
// Avoid adding aliases “just in case”
#[serde(alias = "web_search_request")]
pub web_search: Option<bool>,
  • Dont change override parsing in one crate without aligning others.
// Avoid custom rewraps in TUI only
let raw = cli.config_overrides.raw_overrides.clone();
let cli_kv_overrides = codex_common::CliConfigOverrides { raw_overrides: raw }.parse_overrides()?;
  • Dont silence unknown SSE kinds; retain a debug log for troubleshooting.
// Avoid
_ => {}