mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-10-08 15:43:10 +08:00
fix: restore upstream openai_chat.rs, add only debug logging
This commit is contained in:
@@ -95,12 +95,14 @@ impl Provider for OpenAiChatProvider {
|
|||||||
let mut body = json!({
|
let mut body = json!({
|
||||||
"model": request.model.model_id,
|
"model": request.model.model_id,
|
||||||
"messages": messages,
|
"messages": messages,
|
||||||
"tools": request.prompt.tools.iter().map(|tool| json!({"type":"function","function":{
|
|
||||||
"name": tool.name, "description": tool.description, "parameters": tool.parameters
|
|
||||||
}})).collect::<Vec<_>>(),
|
|
||||||
"stream": true,
|
"stream": true,
|
||||||
"stream_options": {"include_usage": true}
|
"stream_options": {"include_usage": true}
|
||||||
});
|
});
|
||||||
|
if !request.prompt.tools.is_empty() {
|
||||||
|
body["tools"] = json!(request.prompt.tools.iter().map(|tool| json!({"type":"function","function":{
|
||||||
|
"name": tool.name, "description": tool.description, "parameters": tool.parameters
|
||||||
|
}})).collect::<Vec<_>>());
|
||||||
|
}
|
||||||
apply_model(&mut body, &request.model, config.max_output_tokens)?;
|
apply_model(&mut body, &request.model, config.max_output_tokens)?;
|
||||||
merge_extra_params(&mut body, &request.model.extra_params)?;
|
merge_extra_params(&mut body, &request.model.extra_params)?;
|
||||||
apply_openai_prompt_cache_key(&mut body, &request.model.model_id)?;
|
apply_openai_prompt_cache_key(&mut body, &request.model.model_id)?;
|
||||||
@@ -164,42 +166,27 @@ impl Provider for OpenAiChatProvider {
|
|||||||
let mut loop_iteration: u64 = 0;
|
let mut loop_iteration: u64 = 0;
|
||||||
loop {
|
loop {
|
||||||
loop_iteration += 1;
|
loop_iteration += 1;
|
||||||
dbg_log!("openai_chat.rs:stream:poll", "Polling SSE event", serde_json::json!({
|
|
||||||
"iteration": loop_iteration,
|
|
||||||
"saw_done": saw_done_marker,
|
|
||||||
"has_finish": finish.is_some(),
|
|
||||||
"tool_count": tools.len(),
|
|
||||||
"text_open": text_open,
|
|
||||||
"thinking_open": thinking_open,
|
|
||||||
"reasoning_len": reasoning.len()
|
|
||||||
}));
|
|
||||||
let event = tokio::select! {
|
let event = tokio::select! {
|
||||||
_ = cancellation.cancelled() => {
|
_ = cancellation.cancelled() => {
|
||||||
dbg_log!("openai_chat.rs:stream", "Stream cancelled by token", serde_json::json!({
|
dbg_log!("openai_chat.rs:stream", "Stream cancelled by token", serde_json::json!({
|
||||||
"iteration": loop_iteration,
|
"iteration": loop_iteration,
|
||||||
"saw_done_marker": saw_done_marker,
|
"saw_done_marker": saw_done_marker,
|
||||||
"tool_count": tools.len(),
|
"tool_count": tools.len()
|
||||||
"text_open": text_open
|
|
||||||
}));
|
}));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
event = source.next() => event,
|
event = source.next() => event,
|
||||||
};
|
};
|
||||||
let Some(event) = event else {
|
let Some(event) = event else {
|
||||||
dbg_log!("openai_chat.rs:stream:poll", "SSE stream ended (None from source)", serde_json::json!({
|
dbg_log!("openai_chat.rs:stream", "SSE stream ended (None)", serde_json::json!({
|
||||||
"iteration": loop_iteration,
|
"iteration": loop_iteration,
|
||||||
"saw_done_marker": saw_done_marker,
|
"saw_done_marker": saw_done_marker
|
||||||
"has_finish": finish.is_some(),
|
|
||||||
"tool_count": tools.len(),
|
|
||||||
"text_open": text_open,
|
|
||||||
"thinking_open": thinking_open,
|
|
||||||
"reasoning_len": reasoning.len()
|
|
||||||
}));
|
}));
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
let event = event.map_err(|error| {
|
let event = event.map_err(|error| {
|
||||||
let err_msg = error.to_string();
|
let err_msg = error.to_string();
|
||||||
dbg_log!("openai_chat.rs:stream:poll", "SSE event error", serde_json::json!({
|
dbg_log!("openai_chat.rs:stream", "SSE event error", serde_json::json!({
|
||||||
"iteration": loop_iteration,
|
"iteration": loop_iteration,
|
||||||
"error": err_msg.clone()
|
"error": err_msg.clone()
|
||||||
}));
|
}));
|
||||||
@@ -212,7 +199,7 @@ impl Provider for OpenAiChatProvider {
|
|||||||
}
|
}
|
||||||
let Some(choice) = value.get("choices").and_then(Value::as_array).and_then(|values| values.first()) else { continue; };
|
let Some(choice) = value.get("choices").and_then(Value::as_array).and_then(|values| values.first()) else { continue; };
|
||||||
let delta = choice.get("delta").unwrap_or(&Value::Null);
|
let delta = choice.get("delta").unwrap_or(&Value::Null);
|
||||||
if let Some(reasoning_delta) = delta.get("reasoning_content").and_then(Value::as_str).filter(|text| !text.is_empty()) {
|
if let Some(reasoning_delta) = delta.get("reasoning_content").or_else(|| delta.get("reasoning")).and_then(Value::as_str).filter(|text| !text.is_empty()) {
|
||||||
if !thinking_open { thinking_open = true; yield ModelEvent::ThinkingStart; }
|
if !thinking_open { thinking_open = true; yield ModelEvent::ThinkingStart; }
|
||||||
reasoning.push_str(reasoning_delta);
|
reasoning.push_str(reasoning_delta);
|
||||||
yield ModelEvent::ThinkingDelta(reasoning_delta.into());
|
yield ModelEvent::ThinkingDelta(reasoning_delta.into());
|
||||||
@@ -236,15 +223,6 @@ impl Provider for OpenAiChatProvider {
|
|||||||
finish = Some(map_finish(reason, !tools.is_empty()));
|
finish = Some(map_finish(reason, !tools.is_empty()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dbg_log!("openai_chat.rs:stream", "SSE loop exited", serde_json::json!({
|
|
||||||
"total_iterations": loop_iteration,
|
|
||||||
"saw_done_marker": saw_done_marker,
|
|
||||||
"finish_reason": finish.as_ref().map(|f| format!("{:?}", f)),
|
|
||||||
"reasoning_len": reasoning.len(),
|
|
||||||
"tool_count": tools.len(),
|
|
||||||
"text_open": text_open,
|
|
||||||
"thinking_open": thinking_open
|
|
||||||
}));
|
|
||||||
if thinking_open { yield ModelEvent::ThinkingEnd; }
|
if thinking_open { yield ModelEvent::ThinkingEnd; }
|
||||||
if text_open { yield ModelEvent::TextEnd; }
|
if text_open { yield ModelEvent::TextEnd; }
|
||||||
for (index, tool) in &mut tools {
|
for (index, tool) in &mut tools {
|
||||||
@@ -319,12 +297,27 @@ fn openai_chat_messages(instructions: &str, messages: &[ProjectedMessage]) -> Re
|
|||||||
calls,
|
calls,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
value.insert("content".into(), Value::String(text.clone()));
|
|
||||||
let replay_reasoning = replay_state
|
let replay_reasoning = replay_state
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.filter(|state| state.provider_kind == "openai_chat")
|
.filter(|state| state.provider_kind == "openai_chat")
|
||||||
.and_then(|state| state.value.get("reasoning_content"))
|
.and_then(|state| state.value.get("reasoning_content"))
|
||||||
.and_then(Value::as_str);
|
.and_then(Value::as_str)
|
||||||
|
.filter(|reasoning| !reasoning.is_empty());
|
||||||
|
|
||||||
|
// Chat Completions rejects an empty assistant content string. Tool-call
|
||||||
|
// assistant messages use null content, while an assistant with no visible
|
||||||
|
// content at all does not need to be sent.
|
||||||
|
if text.is_empty() && calls.is_empty() && replay_reasoning.is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
value.insert(
|
||||||
|
"content".into(),
|
||||||
|
if text.is_empty() {
|
||||||
|
Value::Null
|
||||||
|
} else {
|
||||||
|
Value::String(text.clone())
|
||||||
|
},
|
||||||
|
);
|
||||||
if let Some(reasoning) = replay_reasoning {
|
if let Some(reasoning) = replay_reasoning {
|
||||||
value.insert("reasoning_content".into(), Value::String(reasoning.into()));
|
value.insert("reasoning_content".into(), Value::String(reasoning.into()));
|
||||||
}
|
}
|
||||||
@@ -487,7 +480,7 @@ mod tests {
|
|||||||
model::{ContentPart, ProjectedContent, ProjectedMessage, ToolResultContent},
|
model::{ContentPart, ProjectedContent, ProjectedMessage, ToolResultContent},
|
||||||
model::{ProviderReplayState, Role, ToolCallContent},
|
model::{ProviderReplayState, Role, ToolCallContent},
|
||||||
};
|
};
|
||||||
use serde_json::json;
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn chat_replay_state_is_encoded_as_reasoning_content() {
|
fn chat_replay_state_is_encoded_as_reasoning_content() {
|
||||||
@@ -519,6 +512,52 @@ mod tests {
|
|||||||
assert_eq!(messages[0]["tool_calls"][0]["id"], "call-1");
|
assert_eq!(messages[0]["tool_calls"][0]["id"], "call-1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn chat_tool_call_assistant_uses_null_content() {
|
||||||
|
let messages = openai_chat_messages(
|
||||||
|
"",
|
||||||
|
&[ProjectedMessage {
|
||||||
|
message_id: "test".into(),
|
||||||
|
role: Role::Assistant,
|
||||||
|
content: ProjectedContent::Assistant {
|
||||||
|
text: String::new(),
|
||||||
|
thinking: String::new(),
|
||||||
|
replay_state: None,
|
||||||
|
calls: vec![ToolCallContent {
|
||||||
|
index: 0,
|
||||||
|
call_id: "call-1".into(),
|
||||||
|
name: "Read".into(),
|
||||||
|
arguments: json!({"path": "README.md"}),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(messages[0]["content"], Value::Null);
|
||||||
|
assert!(messages[0]["tool_calls"].is_array());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn chat_contentless_assistant_is_omitted() {
|
||||||
|
let messages = openai_chat_messages(
|
||||||
|
"",
|
||||||
|
&[ProjectedMessage {
|
||||||
|
message_id: "test".into(),
|
||||||
|
role: Role::Assistant,
|
||||||
|
content: ProjectedContent::Assistant {
|
||||||
|
text: String::new(),
|
||||||
|
thinking: String::new(),
|
||||||
|
replay_state: None,
|
||||||
|
calls: vec![],
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(messages.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn another_provider_replay_does_not_invent_chat_reasoning_content() {
|
fn another_provider_replay_does_not_invent_chat_reasoning_content() {
|
||||||
let messages = openai_chat_messages(
|
let messages = openai_chat_messages(
|
||||||
|
|||||||
Reference in New Issue
Block a user