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

5.2 KiB
Raw Blame History

DOs

  • Emit Begin/End Pair: Fire WebSearchBegin as a marker and include the actual query in WebSearchEnd for UI display.

    // Emission
    sess.tx_event.send(Event {
        id: sub_id.to_string(),
        msg: EventMsg::WebSearchBegin(WebSearchBeginEvent { call_id: call_id.clone() }),
    }).await.ok();
    
    sess.tx_event.send(Event {
        id: sub_id.to_string(),
        msg: EventMsg::WebSearchEnd(WebSearchEndEvent { call_id, query }),
    }).await.ok();
    
  • Validate IDs Strongly: Require a non-empty call_id on WebSearchCall before emitting WebSearchEnd; log and skip otherwise.

    match item {
        ResponseItem::WebSearchCall {
            id: Some(call_id),
            action: WebSearchAction::Search { query },
            ..
        } if !call_id.is_empty() => {
            sess.tx_event.send(Event {
                id: sub_id.to_string(),
                msg: EventMsg::WebSearchEnd(WebSearchEndEvent { call_id, query }),
            }).await.ok();
        }
        ResponseItem::WebSearchCall { .. } => {
            warn!("web_search_call missing call_id; skipping end event");
        }
        _ => {}
    }
    
  • Centralize Tool Mapping: Keep the web_search vs web_search_preview mapping in OpenAiTool (serde rename), and let create_tools_json_for_responses_api only serialize.

    #[derive(Serialize)]
    #[serde(tag = "type", rename_all = "snake_case")]
    enum OpenAiTool {
        #[serde(rename = "web_search_preview")]
        WebSearch {},
    }
    
    pub fn create_tools_json_for_responses_api(
        tools: &[OpenAiTool],
    ) -> anyhow::Result<Vec<serde_json::Value>> {
        tools.iter().map(serde_json::to_value).collect::<Result<_, _>>().map_err(Into::into)
    }
    
    // Client
    let tools_json = create_tools_json_for_responses_api(&prompt.tools)?;
    
  • Keep UI-Sensitive Fields Stable (or Plan Deprecations): If removing query from WebSearchBegin, ensure UI is updated to read it from WebSearchEnd and communicate the change.

  • Exclude From History/Rollouts: Treat WebSearchCall as non-history, non-rollout.

    fn is_api_message(m: &ResponseItem) -> bool {
        match m {
            ResponseItem::WebSearchCall { .. } | ResponseItem::Other => false,
            _ => true,
        }
    }
    
  • Simplify Ownership: Prefer moves and defaults over unnecessary clones in config.

    let history = cfg.history.unwrap_or_default();
    let shell_environment_policy = cfg.shell_environment_policy.into();
    let tui = cfg.tui.unwrap_or_default();
    let chatgpt_base_url = config_profile.chatgpt_base_url
        .or(cfg.chatgpt_base_url)
        .unwrap_or("https://chatgpt.com/backend-api/".to_string());
    
  • Route SSE Explicitly: Handle "response.output_item.added" separately to detect "web_search_call"; avoid lumping with unrelated kinds.

    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();
                    tx_event.send(Ok(ResponseEvent::WebSearchCallBegin { call_id })).await.ok();
                }
            }
        }
        "response.output_text.done" | "response.in_progress" => {}
        _ => {}
    }
    
  • Use Inline format! Captures: Follow repo style for string formatting.

    // DO
    let msg = format!("Searched: {query}");
    
  • Update TUI on End: Flush stream on begin; append a “Searched: …” history cell on end.

    fn on_web_search_begin(&mut self, _: WebSearchBeginEvent) {
        self.flush_answer_stream_with_separator();
    }
    fn on_web_search_end(&mut self, ev: WebSearchEndEvent) {
        let query = ev.query;
        self.add_to_history(history_cell::new_web_search_call(format!("Searched: {query}")));
    }
    

DONTs

  • Dont Default Missing IDs: Avoid let call_id = id.unwrap_or_else(|| "".to_string()); which masks protocol issues.

    // DON'T
    let call_id = id.unwrap_or_else(|| "".to_string());
    // DO
    if let Some(call_id) = id.filter(|s| !s.is_empty()) { /* ... */ }
    
  • Dont Show Query on Begin: The begin event may precede the finalized query; display it on WebSearchEnd only.

  • Dont Duplicate Tool Logic: Dont rewrite the tool type in multiple layers (e.g., client). Keep it in OpenAiTools serde mapping.

  • Dont Persist WebSearchCall: Dont add WebSearchCall items to conversation history or rollout logs.

  • Dont Over-Clone Config: Avoid .clone() on Option/Copy-like config fields when unwrap_or_default, .or(...), and .into() suffice.

  • Dont Use Unsupported Tool Names: Until the API accepts it reliably, dont send "web_search"; use "web_search_preview" via serde rename.

  • Dont Collapse SSE Branches: Dont combine "response.output_item.added" with other event kinds; precise routing prevents regressions.

  • Dont Violate format! Style: Avoid positional formatting when implicit captures are possible.

    // DON'T
    let msg = format!("Searched: {}", query);
    // DO
    let msg = format!("Searched: {query}");