原子化Direct线程与历史回放判定
让thread_for在同一锁内返回线程是否新建。 仅新建Direct线程时读取project.jsonl并注入历史。 保持已有线程只发送当前用户消息。
This commit is contained in:
@@ -1417,11 +1417,6 @@ fn trust_isolated_game_creator_codex_workspace(
|
||||
}
|
||||
|
||||
impl CodexAppServerConnection {
|
||||
async fn has_thread_for(&self, snapshot: &AgentRuntimeProviderRequestSnapshot) -> bool {
|
||||
let key = CodexNodeThreadKey::from(snapshot);
|
||||
self.inner.threads.lock().await.contains_key(&key)
|
||||
}
|
||||
|
||||
async fn acquire(
|
||||
snapshot: &AgentRuntimeProviderRequestSnapshot,
|
||||
llm: &GameCreatorLlmConfig,
|
||||
@@ -1948,17 +1943,23 @@ impl CodexAppServerConnection {
|
||||
snapshot: &AgentRuntimeProviderRequestSnapshot,
|
||||
request: &LlmRunRequest,
|
||||
llm: &GameCreatorLlmConfig,
|
||||
) -> Result<CodexThreadLease, platform_llm::LlmError> {
|
||||
) -> Result<(CodexThreadLease, bool), platform_llm::LlmError> {
|
||||
let key = CodexNodeThreadKey::from(snapshot);
|
||||
let mut threads = self.inner.threads.lock().await;
|
||||
// The caller holds `turn_gate` while invoking this method. Returning
|
||||
// the creation bit from the same threads lock keeps the replay
|
||||
// decision atomic with thread reuse/creation.
|
||||
if let Some(entry) = threads.get_mut(&key) {
|
||||
entry.active_uses = entry.active_uses.saturating_add(1);
|
||||
entry.last_used = next_game_creator_codex_app_server_usage_tick();
|
||||
return Ok(CodexThreadLease {
|
||||
connection: self.clone(),
|
||||
key,
|
||||
thread_id: entry.thread_id.clone(),
|
||||
});
|
||||
return Ok((
|
||||
CodexThreadLease {
|
||||
connection: self.clone(),
|
||||
key,
|
||||
thread_id: entry.thread_id.clone(),
|
||||
},
|
||||
false,
|
||||
));
|
||||
}
|
||||
if threads.len() >= GAME_CREATOR_CODEX_APP_SERVER_THREAD_MAX {
|
||||
let evict_key = threads
|
||||
@@ -2020,11 +2021,14 @@ impl CodexAppServerConnection {
|
||||
active_uses: 1,
|
||||
},
|
||||
);
|
||||
Ok(CodexThreadLease {
|
||||
connection: self.clone(),
|
||||
key,
|
||||
thread_id,
|
||||
})
|
||||
Ok((
|
||||
CodexThreadLease {
|
||||
connection: self.clone(),
|
||||
key,
|
||||
thread_id,
|
||||
},
|
||||
true,
|
||||
))
|
||||
}
|
||||
|
||||
async fn register_turn(&self, turn_id: &str) -> mpsc::UnboundedReceiver<CodexTurnEvent> {
|
||||
@@ -2075,6 +2079,7 @@ impl CodexAppServerConnection {
|
||||
snapshot,
|
||||
llm,
|
||||
request,
|
||||
None,
|
||||
on_agent_message_delta,
|
||||
None,
|
||||
None,
|
||||
@@ -2087,14 +2092,37 @@ impl CodexAppServerConnection {
|
||||
snapshot: &AgentRuntimeProviderRequestSnapshot,
|
||||
llm: &GameCreatorLlmConfig,
|
||||
request: LlmRunRequest,
|
||||
direct_client_turn_id: Option<&str>,
|
||||
mut on_agent_message_delta: Option<&mut (dyn FnMut(&platform_llm::LlmStreamDelta) + Send)>,
|
||||
mut direct_observer: Option<&mut (dyn FnMut(DirectCodexTurnObservation) + Send)>,
|
||||
mut audit: Option<&mut DirectCodexTurnAudit>,
|
||||
) -> Result<platform_llm::LlmRunResponse, platform_llm::LlmError> {
|
||||
let _turn_guard = self.inner.turn_gate.lock().await;
|
||||
let thread_lease = self.thread_for(snapshot, &request, llm).await?;
|
||||
let (thread_lease, thread_created) = self.thread_for(snapshot, &request, llm).await?;
|
||||
self.wait_for_initial_client_mcp_startup().await;
|
||||
let thread_id = thread_lease.thread_id.clone();
|
||||
let mut request = request;
|
||||
if thread_created && self.inner.workspace_mode.uses_direct_conversation() {
|
||||
// DirectProject intentionally replays the complete append-only
|
||||
// project history. AGC owns that durable fact source; keeping
|
||||
// the replay lossless is the product contract even though a
|
||||
// future context-budget policy may need to change this choice.
|
||||
let current_prompt = direct_codex_current_user_prompt(&request).to_string();
|
||||
let history_prompt = build_direct_codex_history_prompt(
|
||||
&self.inner.workspace_path,
|
||||
direct_client_turn_id.unwrap_or("__none__"),
|
||||
¤t_prompt,
|
||||
)
|
||||
.map_err(platform_llm::LlmError::InvalidRequest)?;
|
||||
if let Some(message) = request
|
||||
.messages
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find(|message| message.role == LlmMessageRole::User)
|
||||
{
|
||||
message.content = history_prompt;
|
||||
}
|
||||
}
|
||||
let prompt = if self.inner.workspace_mode.uses_direct_conversation() {
|
||||
direct_codex_user_prompt(&request)
|
||||
} else {
|
||||
@@ -3145,22 +3173,21 @@ pub(crate) async fn direct_game_creator_codex_chat_at_with_optional_observer(
|
||||
)
|
||||
.await
|
||||
.map_err(|error| error.to_string())?;
|
||||
let user_prompt = if connection.has_thread_for(&snapshot).await {
|
||||
user_prompt
|
||||
} else {
|
||||
build_direct_codex_history_prompt(
|
||||
&codex_root,
|
||||
client_turn_id.unwrap_or("__none__"),
|
||||
&user_prompt,
|
||||
)?
|
||||
};
|
||||
let request = LlmRunRequest::single_turn(system_prompt, user_prompt)
|
||||
.with_api_kind(api_kind)
|
||||
.with_model(config.llm.model.clone())
|
||||
.with_request_timeout_ms(config.llm.request_timeout_ms)
|
||||
.with_max_output_tokens(16_000);
|
||||
connection
|
||||
.run_turn_with_direct_observer(&snapshot, &config.llm, request, None, observer, audit)
|
||||
.run_turn_with_direct_observer(
|
||||
&snapshot,
|
||||
&config.llm,
|
||||
request,
|
||||
client_turn_id,
|
||||
None,
|
||||
observer,
|
||||
audit,
|
||||
)
|
||||
.await
|
||||
.map(|value| value.text)
|
||||
.map_err(|error| error.to_string())
|
||||
@@ -3173,6 +3200,9 @@ pub(crate) fn build_direct_codex_history_prompt(
|
||||
) -> Result<String, String> {
|
||||
let conversation = read_local_conversation_for_session_at(root, None, None)?;
|
||||
let current_message_id = format!("direct-codex:{client_turn_id}:user");
|
||||
// The deliberately simple role-prefix format is part of the Direct
|
||||
// replay contract. Do not introduce an envelope or implicit escaping
|
||||
// here without updating the persisted-history compatibility decision.
|
||||
let mut lines = conversation
|
||||
.messages
|
||||
.iter()
|
||||
@@ -4622,6 +4652,7 @@ while IFS= read -r line; do :; done
|
||||
&test_snapshot(),
|
||||
&llm,
|
||||
tool_request(),
|
||||
None,
|
||||
Some(&mut on_delta),
|
||||
Some(&mut observer),
|
||||
None,
|
||||
|
||||
Reference in New Issue
Block a user