清理已退役的Direct审计与计时代码
删除旧平行审计和请求计时模块及沿途参数 保留代理透传、模型记录与现役历史链路测试 同步契约和验证记录,消除12条编译警告
This commit is contained in:
@@ -15,7 +15,6 @@ mod codex_provider_proxy;
|
||||
mod design_runtime;
|
||||
pub(crate) mod design_tools;
|
||||
mod direct_codex_attachments;
|
||||
mod direct_codex_audit;
|
||||
mod direct_codex_user_item;
|
||||
mod direct_execution;
|
||||
pub(crate) use direct_execution::WritePermit;
|
||||
@@ -34,7 +33,6 @@ mod direct_thread_wire;
|
||||
mod direct_tool_bridge;
|
||||
mod direct_tool_calls;
|
||||
mod direct_tools_mcp;
|
||||
mod direct_turn_metrics;
|
||||
mod direct_turn_stream;
|
||||
mod direct_validation;
|
||||
mod generation;
|
||||
@@ -61,7 +59,6 @@ pub(crate) use codex_cli::{
|
||||
pub(crate) use codex_provider_proxy::*;
|
||||
pub(crate) use design_runtime::*;
|
||||
pub(crate) use direct_codex_attachments::*;
|
||||
pub(crate) use direct_codex_audit::*;
|
||||
pub(crate) use direct_codex_user_item::*;
|
||||
pub(crate) use direct_project_history::*;
|
||||
pub(crate) use direct_project_turn_history::*;
|
||||
@@ -71,7 +68,6 @@ pub(crate) use direct_thread_wire::*;
|
||||
pub(crate) use direct_tool_bridge::*;
|
||||
pub(crate) use direct_tool_calls::*;
|
||||
pub(crate) use direct_tools_mcp::*;
|
||||
pub(crate) use direct_turn_metrics::*;
|
||||
pub(crate) use direct_turn_stream::*;
|
||||
pub(crate) use direct_validation::DirectValidationConfig;
|
||||
pub(crate) use generation::*;
|
||||
|
||||
@@ -3219,15 +3219,8 @@ impl CodexAppServerConnection {
|
||||
request: LlmRunRequest,
|
||||
on_agent_message_delta: Option<&mut (dyn FnMut(&platform_llm::LlmStreamDelta) + Send)>,
|
||||
) -> Result<platform_llm::LlmRunResponse, platform_llm::LlmError> {
|
||||
self.run_turn_with_direct_observer(
|
||||
snapshot,
|
||||
llm,
|
||||
request,
|
||||
on_agent_message_delta,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
self.run_turn_with_direct_observer(snapshot, llm, request, on_agent_message_delta, None)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn run_turn_with_direct_observer(
|
||||
@@ -3237,7 +3230,6 @@ impl CodexAppServerConnection {
|
||||
request: LlmRunRequest,
|
||||
on_agent_message_delta: Option<&mut (dyn FnMut(&platform_llm::LlmStreamDelta) + Send)>,
|
||||
direct_observer: Option<&mut (dyn FnMut(DirectCodexTurnObservation) + Send)>,
|
||||
audit: Option<&mut DirectCodexTurnAudit>,
|
||||
) -> Result<platform_llm::LlmRunResponse, platform_llm::LlmError> {
|
||||
self.run_turn_with_direct_observer_and_history(
|
||||
snapshot,
|
||||
@@ -3249,8 +3241,6 @@ impl CodexAppServerConnection {
|
||||
DirectCodexTurnKind::User,
|
||||
on_agent_message_delta,
|
||||
direct_observer,
|
||||
audit,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -3266,27 +3256,8 @@ impl CodexAppServerConnection {
|
||||
turn_kind: DirectCodexTurnKind,
|
||||
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>,
|
||||
metrics_attempt: Option<DirectMetricAttempt>,
|
||||
) -> Result<platform_llm::LlmRunResponse, platform_llm::LlmError> {
|
||||
let mut gate_timing = metrics_attempt
|
||||
.as_ref()
|
||||
.map(|attempt| attempt.span("local-turn-gate"));
|
||||
let _turn_guard = self.inner.turn_gate.lock().await;
|
||||
if let Some(timing) = gate_timing.as_mut() {
|
||||
timing.finish("acquired");
|
||||
}
|
||||
// Scope only after acquiring the per-connection gate. Requests clone the binding
|
||||
// at ingress, so a late body never borrows the next turn's identity.
|
||||
let _metrics_binding = metrics_attempt.as_ref().and_then(|attempt| {
|
||||
match self.inner._provider_proxy.as_ref() {
|
||||
Some(proxy) => Some(proxy.bind_metrics(attempt.clone())),
|
||||
None => {
|
||||
attempt.route(DirectMetricRoute::AppServerAuth);
|
||||
None
|
||||
}
|
||||
}
|
||||
});
|
||||
let history_root = direct_history_root.unwrap_or(&self.inner.workspace_path);
|
||||
// 工具调用卡片的 turnId 用 AGC 客户端回合 id(与实时事件、落盘条目同一口径),
|
||||
// 不用 Codex app-server 自己的 turnId——前端要按它把卡片挂回对应的那一轮。
|
||||
@@ -3403,21 +3374,11 @@ impl CodexAppServerConnection {
|
||||
if thread_created && self.inner.workspace_mode == CodexAppServerWorkspaceMode::DirectProject
|
||||
{
|
||||
if let Some(client_turn_id) = direct_client_turn_id {
|
||||
let mut prefetch_timing = metrics_attempt
|
||||
.as_ref()
|
||||
.map(|attempt| attempt.span("project-context-prefetch"));
|
||||
let prefetched = super::direct_project_context::prefetch_turn_input(
|
||||
history_root,
|
||||
client_turn_id,
|
||||
)
|
||||
.await;
|
||||
if let Some(timing) = prefetch_timing.as_mut() {
|
||||
timing.finish(if prefetched.is_ok() {
|
||||
"completed"
|
||||
} else {
|
||||
"failed"
|
||||
});
|
||||
}
|
||||
match prefetched {
|
||||
Ok(Some(context)) => {
|
||||
if let Some(parts) = input.as_array_mut() {
|
||||
@@ -3505,9 +3466,6 @@ impl CodexAppServerConnection {
|
||||
cancellation: Arc::clone(&turn_start_cancellation),
|
||||
armed: true,
|
||||
};
|
||||
let mut start_timing = metrics_attempt
|
||||
.as_ref()
|
||||
.map(|attempt| attempt.span("turn-start-ack"));
|
||||
let result = match self
|
||||
.request_with_turn_start_cancellation(
|
||||
"turn/start",
|
||||
@@ -3516,16 +3474,8 @@ impl CodexAppServerConnection {
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(result) => {
|
||||
if let Some(timing) = start_timing.as_mut() {
|
||||
timing.finish("acknowledged");
|
||||
}
|
||||
result
|
||||
}
|
||||
Ok(result) => result,
|
||||
Err(error) => {
|
||||
if let Some(timing) = start_timing.as_mut() {
|
||||
timing.finish("failed");
|
||||
}
|
||||
if let Some(adapter) = approval_adapter
|
||||
.as_ref()
|
||||
.filter(|adapter| adapter.is_host_ending())
|
||||
@@ -3661,18 +3611,8 @@ impl CodexAppServerConnection {
|
||||
.await);
|
||||
}
|
||||
};
|
||||
if event.is_some() {
|
||||
if let Some(attempt) = metrics_attempt.as_ref() {
|
||||
attempt.observe_app_event("first-event");
|
||||
}
|
||||
}
|
||||
match event {
|
||||
Some(CodexTurnEvent::AgentMessageDelta { item_id, delta }) => {
|
||||
if !delta.is_empty() {
|
||||
if let Some(attempt) = metrics_attempt.as_ref() {
|
||||
attempt.observe_app_event("first-content-delta");
|
||||
}
|
||||
}
|
||||
if self.inner.workspace_mode == CodexAppServerWorkspaceMode::DirectProject {
|
||||
direct_project_history.observe_delta(&item_id, &delta);
|
||||
append_direct_thread_event(
|
||||
@@ -3716,11 +3656,6 @@ impl CodexAppServerConnection {
|
||||
}
|
||||
}
|
||||
Some(CodexTurnEvent::ReasoningDelta { item_id, delta }) => {
|
||||
if !delta.is_empty() {
|
||||
if let Some(attempt) = metrics_attempt.as_ref() {
|
||||
attempt.observe_app_event("first-reasoning-delta");
|
||||
}
|
||||
}
|
||||
if self.inner.workspace_mode == CodexAppServerWorkspaceMode::DirectProject {
|
||||
append_direct_thread_event(
|
||||
&direct_thread_id,
|
||||
@@ -3739,9 +3674,6 @@ impl CodexAppServerConnection {
|
||||
}
|
||||
}
|
||||
Some(CodexTurnEvent::RawItem(item)) => {
|
||||
if let Some(attempt) = metrics_attempt.as_ref() {
|
||||
attempt.observe_raw_item(&item);
|
||||
}
|
||||
if self.inner.workspace_mode == CodexAppServerWorkspaceMode::DirectProject {
|
||||
if item.is_null() {
|
||||
return Err(platform_llm::LlmError::Deserialize(
|
||||
@@ -3796,9 +3728,6 @@ impl CodexAppServerConnection {
|
||||
}
|
||||
Some(CodexTurnEvent::Item { completed, params }) => {
|
||||
if let Some(item) = params.get("item") {
|
||||
if let Some(attempt) = metrics_attempt.as_ref() {
|
||||
attempt.observe_item(item, completed);
|
||||
}
|
||||
let item_type = item
|
||||
.get("type")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
@@ -3849,11 +3778,6 @@ impl CodexAppServerConnection {
|
||||
}
|
||||
}
|
||||
}
|
||||
if completed {
|
||||
if let Some(audit) = audit.as_mut() {
|
||||
audit.observe_item(¶ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
if item_type == "agentMessage" {
|
||||
// 某些 app-server 实现会在工具开始后停止发送 agentMessage delta,
|
||||
@@ -3987,9 +3911,6 @@ impl CodexAppServerConnection {
|
||||
}
|
||||
return execution::outcome_text(adapter.wait_outcome().await);
|
||||
}
|
||||
if let Some(attempt) = metrics_attempt.as_ref() {
|
||||
attempt.finish("interrupted");
|
||||
}
|
||||
return Err(platform_llm::LlmError::InvalidRequest(
|
||||
"Codex app-server turn 已中断".to_string(),
|
||||
));
|
||||
@@ -5073,7 +4994,6 @@ pub(crate) async fn direct_game_creator_codex_chat_at(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -5092,7 +5012,6 @@ pub(crate) async fn direct_game_creator_codex_chat_at_with_observer(
|
||||
None,
|
||||
Some(observer),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -5104,7 +5023,6 @@ pub(crate) async fn direct_game_creator_codex_chat_at_with_optional_observer(
|
||||
turn_kind: DirectCodexTurnKind,
|
||||
client_turn_id: Option<&str>,
|
||||
observer: Option<&mut (dyn FnMut(DirectCodexTurnObservation) + Send)>,
|
||||
audit: Option<&mut DirectCodexTurnAudit>,
|
||||
direct_user_item: Option<serde_json::Value>,
|
||||
) -> Result<String, String> {
|
||||
// Resolve project authority before deriving the pool/thread identity. A
|
||||
@@ -5157,16 +5075,6 @@ pub(crate) async fn direct_game_creator_codex_chat_at_with_optional_observer(
|
||||
};
|
||||
let api_kind =
|
||||
parse_game_creator_llm_api_kind(&config.llm.api_kind).map_err(|error| error.to_string())?;
|
||||
let metrics_attempt = audit.as_ref().map(|audit| {
|
||||
audit.metrics().attempt(
|
||||
&config.llm.model,
|
||||
&config.llm.model,
|
||||
&config.llm.reasoning_effort,
|
||||
)
|
||||
});
|
||||
let mut connection_timing = metrics_attempt
|
||||
.as_ref()
|
||||
.map(|attempt| attempt.span("connection-preparation"));
|
||||
let connection = Box::pin(CodexAppServerConnection::acquire_at_workspace(
|
||||
&snapshot,
|
||||
&config.llm,
|
||||
@@ -5174,26 +5082,14 @@ pub(crate) async fn direct_game_creator_codex_chat_at_with_optional_observer(
|
||||
CodexAppServerWorkspaceMode::DirectProject,
|
||||
effective_client_turn_id,
|
||||
))
|
||||
.await;
|
||||
if let Some(timing) = connection_timing.as_mut() {
|
||||
timing.finish(if connection.is_ok() {
|
||||
"ready"
|
||||
} else {
|
||||
"failed"
|
||||
});
|
||||
}
|
||||
let connection = connection.map_err(|error| {
|
||||
if let Some(attempt) = metrics_attempt.as_ref() {
|
||||
attempt.finish("failed");
|
||||
}
|
||||
error.to_string()
|
||||
})?;
|
||||
.await
|
||||
.map_err(|error| error.to_string())?;
|
||||
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);
|
||||
let result = connection
|
||||
connection
|
||||
.run_turn_with_direct_observer_and_history(
|
||||
&snapshot,
|
||||
&config.llm,
|
||||
@@ -5204,20 +5100,10 @@ pub(crate) async fn direct_game_creator_codex_chat_at_with_optional_observer(
|
||||
turn_kind,
|
||||
None,
|
||||
observer,
|
||||
audit,
|
||||
metrics_attempt.clone(),
|
||||
)
|
||||
.await
|
||||
.map(|value| value.text)
|
||||
.map_err(|error| error.to_string());
|
||||
if let Some(attempt) = metrics_attempt.as_ref() {
|
||||
attempt.finish(if result.is_ok() {
|
||||
"completed"
|
||||
} else {
|
||||
"failed"
|
||||
});
|
||||
}
|
||||
result
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
/// Direct home-page chat never binds Codex to a user project. It gets a
|
||||
@@ -5383,7 +5269,6 @@ mod tests {
|
||||
Some("not-executed"),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let sizes = (
|
||||
std::mem::size_of_val(&spawn),
|
||||
@@ -7612,7 +7497,6 @@ while IFS= read -r line; do :; done
|
||||
tool_request(),
|
||||
Some(&mut on_delta),
|
||||
Some(&mut observer),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("run fake app-server turn");
|
||||
@@ -7741,7 +7625,6 @@ while IFS= read -r line; do :; done
|
||||
tool_request(),
|
||||
None,
|
||||
Some(&mut observer),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("run fake app-server turn");
|
||||
@@ -7861,8 +7744,6 @@ done
|
||||
DirectCodexTurnKind::User,
|
||||
None,
|
||||
Some(&mut observer),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("run direct-project turn");
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use super::{DirectMetricAttempt, DirectMetricRoute, DirectRequestTiming};
|
||||
use axum::body::{to_bytes, Body};
|
||||
use axum::extract::State;
|
||||
use axum::http::{HeaderMap, HeaderName, Request, Response, StatusCode};
|
||||
@@ -28,7 +27,6 @@ struct CodexProviderProxyState {
|
||||
downstream_bearer_token: String,
|
||||
main_site_upstream: bool,
|
||||
client: reqwest::Client,
|
||||
metrics_scope: Arc<Mutex<Option<DirectMetricAttempt>>>,
|
||||
parallel_tool_calls: bool,
|
||||
model_usage: ActiveModelUsage,
|
||||
}
|
||||
@@ -37,8 +35,6 @@ pub(crate) struct CodexProviderProxy {
|
||||
base_url: String,
|
||||
downstream_bearer_token: String,
|
||||
task: tokio::task::JoinHandle<()>,
|
||||
metrics_scope: Arc<Mutex<Option<DirectMetricAttempt>>>,
|
||||
main_site_upstream: bool,
|
||||
model_usage: ActiveModelUsage,
|
||||
}
|
||||
|
||||
@@ -71,21 +67,6 @@ impl CodexProviderProxy {
|
||||
&self.downstream_bearer_token
|
||||
}
|
||||
|
||||
pub(crate) fn bind_metrics(&self, attempt: DirectMetricAttempt) -> CodexProviderMetricsBinding {
|
||||
attempt.route(if self.main_site_upstream {
|
||||
DirectMetricRoute::MainSite
|
||||
} else {
|
||||
DirectMetricRoute::ProviderProxy
|
||||
});
|
||||
if let Ok(mut scope) = self.metrics_scope.lock() {
|
||||
*scope = Some(attempt.clone());
|
||||
}
|
||||
CodexProviderMetricsBinding {
|
||||
scope: Arc::clone(&self.metrics_scope),
|
||||
attempt_id: attempt.id().to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn begin_model_usage(
|
||||
&self,
|
||||
context: crate::project::ProjectModelUsageContext,
|
||||
@@ -102,32 +83,12 @@ impl CodexProviderProxy {
|
||||
}
|
||||
}
|
||||
|
||||
/// A late stream owns its original attempt; releasing a binding cannot clear a new one.
|
||||
pub(crate) struct CodexProviderMetricsBinding {
|
||||
scope: Arc<Mutex<Option<DirectMetricAttempt>>>,
|
||||
attempt_id: String,
|
||||
}
|
||||
|
||||
impl Drop for CodexProviderMetricsBinding {
|
||||
fn drop(&mut self) {
|
||||
if let Ok(mut scope) = self.scope.lock() {
|
||||
if scope
|
||||
.as_ref()
|
||||
.is_some_and(|attempt| attempt.id() == self.attempt_id)
|
||||
{
|
||||
*scope = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MeasuredResponseStream<S> {
|
||||
struct ObservedResponseStream<S> {
|
||||
inner: Pin<Box<S>>,
|
||||
timing: Option<DirectRequestTiming>,
|
||||
observer: Option<ModelResponseObserver>,
|
||||
observer: ModelResponseObserver,
|
||||
}
|
||||
|
||||
impl<S> Stream for MeasuredResponseStream<S>
|
||||
impl<S> Stream for ObservedResponseStream<S>
|
||||
where
|
||||
S: Stream<Item = Result<axum::body::Bytes, reqwest::Error>>,
|
||||
{
|
||||
@@ -137,32 +98,17 @@ where
|
||||
let this = self.get_mut();
|
||||
match this.inner.as_mut().poll_next(cx) {
|
||||
Poll::Ready(Some(Ok(bytes))) => {
|
||||
if let Some(timing) = this.timing.as_mut() {
|
||||
timing.chunk(&bytes);
|
||||
}
|
||||
if let Some(observer) = this.observer.as_mut() {
|
||||
observer.observe(&bytes);
|
||||
}
|
||||
this.observer.observe(&bytes);
|
||||
Poll::Ready(Some(Ok(bytes)))
|
||||
}
|
||||
Poll::Ready(Some(Err(_))) => {
|
||||
if let Some(timing) = this.timing.as_mut() {
|
||||
timing.finish("stream-error");
|
||||
}
|
||||
if let Some(observer) = this.observer.as_mut() {
|
||||
observer.failed();
|
||||
}
|
||||
this.observer.failed();
|
||||
Poll::Ready(Some(Err(std::io::Error::other(
|
||||
"provider response stream failed",
|
||||
))))
|
||||
}
|
||||
Poll::Ready(None) => {
|
||||
if let Some(timing) = this.timing.as_mut() {
|
||||
timing.finish("eof");
|
||||
}
|
||||
if let Some(observer) = this.observer.as_mut() {
|
||||
observer.finish();
|
||||
}
|
||||
this.observer.finish();
|
||||
Poll::Ready(None)
|
||||
}
|
||||
Poll::Pending => Poll::Pending,
|
||||
@@ -277,12 +223,6 @@ async fn proxy_codex_provider_request(
|
||||
if request.method() != axum::http::Method::POST || request.uri().path() != "/responses" {
|
||||
return proxy_error(StatusCode::NOT_FOUND, "provider proxy route not found");
|
||||
}
|
||||
let mut timing = state
|
||||
.metrics_scope
|
||||
.lock()
|
||||
.ok()
|
||||
.and_then(|scope| scope.clone())
|
||||
.map(DirectRequestTiming::new);
|
||||
// 在读请求体或等待上游之前冻结归属,迟到响应不能使用下一回合的项目上下文。
|
||||
let model_usage = state
|
||||
.model_usage
|
||||
@@ -293,32 +233,21 @@ async fn proxy_codex_provider_request(
|
||||
let (parts, body) = request.into_parts();
|
||||
let body = match to_bytes(body, CODEX_PROVIDER_PROXY_MAX_REQUEST_BYTES).await {
|
||||
Ok(body) => body,
|
||||
Err(_) => {
|
||||
if let Some(timing) = timing.as_mut() {
|
||||
timing.finish("request-body-error");
|
||||
}
|
||||
return proxy_error(StatusCode::PAYLOAD_TOO_LARGE, "provider request too large");
|
||||
}
|
||||
Err(_) => return proxy_error(StatusCode::PAYLOAD_TOO_LARGE, "provider request too large"),
|
||||
};
|
||||
let body = if state.parallel_tool_calls {
|
||||
match tokio::task::spawn_blocking(move || parallel_direct_request(&body)).await {
|
||||
Ok(Ok(bytes)) => axum::body::Bytes::from(bytes),
|
||||
_ => {
|
||||
if let Some(timing) = timing.as_mut() {
|
||||
timing.finish("request-body-error");
|
||||
}
|
||||
return proxy_error(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"provider request JSON invalid or oversized",
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
body
|
||||
};
|
||||
if let Some(timing) = timing.as_mut() {
|
||||
timing.request_body(&body);
|
||||
}
|
||||
let mut headers = HeaderMap::new();
|
||||
for (name, value) in &parts.headers {
|
||||
if !is_hop_by_hop_header(name) && name != axum::http::header::AUTHORIZATION {
|
||||
@@ -336,19 +265,13 @@ async fn proxy_codex_provider_request(
|
||||
let upstream_authorization = match format!("Bearer {}", state.upstream_bearer_token).parse() {
|
||||
Ok(value) => value,
|
||||
Err(_) => {
|
||||
if let Some(timing) = timing.as_mut() {
|
||||
timing.finish("invalid-credential");
|
||||
}
|
||||
return proxy_error(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"provider proxy credential invalid",
|
||||
);
|
||||
)
|
||||
}
|
||||
};
|
||||
headers.insert(axum::http::header::AUTHORIZATION, upstream_authorization);
|
||||
if let Some(timing) = timing.as_mut() {
|
||||
timing.dispatched();
|
||||
}
|
||||
let upstream = match state
|
||||
.client
|
||||
.request(parts.method, upstream_url)
|
||||
@@ -358,32 +281,14 @@ async fn proxy_codex_provider_request(
|
||||
.await
|
||||
{
|
||||
Ok(response) => response,
|
||||
Err(_) => {
|
||||
if let Some(timing) = timing.as_mut() {
|
||||
timing.finish("upstream-error");
|
||||
}
|
||||
return proxy_error(StatusCode::BAD_GATEWAY, "provider upstream unavailable");
|
||||
}
|
||||
Err(_) => return proxy_error(StatusCode::BAD_GATEWAY, "provider upstream unavailable"),
|
||||
};
|
||||
let status = upstream.status();
|
||||
let upstream_headers = upstream.headers().clone();
|
||||
if let Some(timing) = timing.as_mut() {
|
||||
let sse = upstream_headers
|
||||
.get("content-type")
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.is_some_and(|value| {
|
||||
value
|
||||
.split(';')
|
||||
.next()
|
||||
.is_some_and(|value| value.trim().eq_ignore_ascii_case("text/event-stream"))
|
||||
});
|
||||
timing.headers(status.as_u16(), sse);
|
||||
}
|
||||
let observer = ModelResponseObserver::new(model_usage, status, &upstream_headers);
|
||||
let stream = MeasuredResponseStream {
|
||||
let stream = ObservedResponseStream {
|
||||
inner: Box::pin(upstream.bytes_stream()),
|
||||
timing,
|
||||
observer: Some(observer),
|
||||
observer,
|
||||
};
|
||||
let mut response = Response::builder().status(status);
|
||||
if let Some(headers) = response.headers_mut() {
|
||||
@@ -453,7 +358,6 @@ pub(crate) async fn start_codex_provider_proxy_with_parallel(
|
||||
let address = listener
|
||||
.local_addr()
|
||||
.map_err(|error| format!("读取 Codex Provider 代理地址失败:{error}"))?;
|
||||
let metrics_scope = Arc::new(Mutex::new(None));
|
||||
let model_usage = Arc::new(Mutex::new(None));
|
||||
let state = Arc::new(CodexProviderProxyState {
|
||||
upstream_base_url,
|
||||
@@ -461,7 +365,6 @@ pub(crate) async fn start_codex_provider_proxy_with_parallel(
|
||||
downstream_bearer_token: downstream_bearer_token.clone(),
|
||||
main_site_upstream,
|
||||
client,
|
||||
metrics_scope: Arc::clone(&metrics_scope),
|
||||
parallel_tool_calls,
|
||||
model_usage: Arc::clone(&model_usage),
|
||||
});
|
||||
@@ -475,8 +378,6 @@ pub(crate) async fn start_codex_provider_proxy_with_parallel(
|
||||
base_url: format!("http://127.0.0.1:{}", address.port()),
|
||||
downstream_bearer_token,
|
||||
task,
|
||||
metrics_scope,
|
||||
main_site_upstream,
|
||||
model_usage,
|
||||
})
|
||||
}
|
||||
@@ -488,30 +389,8 @@ mod tests {
|
||||
use futures::StreamExt;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
fn timing_log_path(root: &std::path::Path) -> std::path::PathBuf {
|
||||
root.join(".agent/runtime/direct-codex/turns/turn.jsonl")
|
||||
}
|
||||
|
||||
fn timing_records(root: &std::path::Path) -> Vec<serde_json::Value> {
|
||||
std::fs::read_to_string(timing_log_path(root))
|
||||
.unwrap()
|
||||
.lines()
|
||||
.map(|line| serde_json::from_str(line).unwrap())
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn measured_stream_preserves_bytes_and_records_eof_after_fragmented_sse() {
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
let metrics =
|
||||
super::super::DirectTurnMetrics::new(timing_log_path(root.path()), "turn-stream");
|
||||
let attempt = metrics.attempt("gpt-5.6-sol", "gpt-5.6-sol", "high");
|
||||
let mut timing = DirectRequestTiming::new(attempt.clone());
|
||||
timing.request_body(
|
||||
br#"{"model":"gpt-5.6-sol","reasoning":{"effort":"high"},"input":"private"}"#,
|
||||
);
|
||||
timing.dispatched();
|
||||
timing.headers(200, true);
|
||||
async fn observed_stream_preserves_fragmented_sse_bytes() {
|
||||
let chunks = [
|
||||
b"data: {\"type\":\"response.created\",\"response\":{\"model\":\"gpt-5.6-sol\"}}\n\n"
|
||||
.as_slice(),
|
||||
@@ -519,151 +398,41 @@ mod tests {
|
||||
b"ta\":\"private content\"}\n\ndata: {\"type\":\"response.completed\"}\n\n".as_slice(),
|
||||
];
|
||||
let expected: Vec<u8> = chunks.concat();
|
||||
let mut stream = MeasuredResponseStream {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("content-type", "text/event-stream".parse().unwrap());
|
||||
let mut stream = ObservedResponseStream {
|
||||
inner: Box::pin(futures::stream::iter(chunks.into_iter().map(|bytes| {
|
||||
Ok::<_, reqwest::Error>(axum::body::Bytes::copy_from_slice(bytes))
|
||||
}))),
|
||||
timing: Some(timing),
|
||||
observer: None,
|
||||
observer: ModelResponseObserver::new(None, StatusCode::OK, &headers),
|
||||
};
|
||||
let mut actual = Vec::new();
|
||||
while let Some(chunk) = stream.next().await {
|
||||
actual.extend_from_slice(&chunk.unwrap());
|
||||
}
|
||||
assert_eq!(actual, expected);
|
||||
drop(stream);
|
||||
assert!(
|
||||
metrics.wait_for_test_writes().await,
|
||||
"writer failed: {}",
|
||||
metrics.snapshot()
|
||||
);
|
||||
let records = timing_records(root.path());
|
||||
let requests: Vec<_> = records
|
||||
.iter()
|
||||
.filter(|row| row["recordType"] == "direct.codex.request_timing")
|
||||
.collect();
|
||||
assert_eq!(requests.len(), 1);
|
||||
let request = requests[0];
|
||||
assert_eq!(request["transportStatus"], "eof");
|
||||
assert_eq!(request["responseStatus"], "completed");
|
||||
assert_eq!(request["responseReportedModel"], "gpt-5.6-sol");
|
||||
assert!(request["firstSseEventOffsetMs"].is_number());
|
||||
assert!(request["firstContentDeltaOffsetMs"].is_number());
|
||||
assert!(!serde_json::to_string(&records).unwrap().contains("private"));
|
||||
assert_eq!(
|
||||
metrics.snapshot()["categories"]["http-request"]["activeCount"],
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn measured_stream_records_errors_and_unpolled_body_drop_without_fake_first_chunk() {
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
let metrics =
|
||||
super::super::DirectTurnMetrics::new(timing_log_path(root.path()), "turn-errors");
|
||||
let attempt = metrics.attempt("gpt-5.6-sol", "gpt-5.6-sol", "high");
|
||||
async fn observed_stream_propagates_upstream_error() {
|
||||
// Invalid URL fails in reqwest's request builder; no network call is made.
|
||||
let error = reqwest::Client::new()
|
||||
.get("not a URL")
|
||||
.send()
|
||||
.await
|
||||
.unwrap_err();
|
||||
let mut stream = MeasuredResponseStream {
|
||||
let mut stream = ObservedResponseStream {
|
||||
inner: Box::pin(futures::stream::iter(vec![Err::<axum::body::Bytes, _>(
|
||||
error,
|
||||
)])),
|
||||
timing: Some(DirectRequestTiming::new(attempt.clone())),
|
||||
observer: None,
|
||||
observer: ModelResponseObserver::new(None, StatusCode::OK, &HeaderMap::new()),
|
||||
};
|
||||
assert!(stream.next().await.unwrap().is_err());
|
||||
drop(stream);
|
||||
let never_polled = MeasuredResponseStream {
|
||||
inner: Box::pin(futures::stream::pending::<
|
||||
Result<axum::body::Bytes, reqwest::Error>,
|
||||
>()),
|
||||
timing: Some(DirectRequestTiming::new(attempt)),
|
||||
observer: None,
|
||||
};
|
||||
drop(never_polled);
|
||||
assert!(
|
||||
metrics.wait_for_test_writes().await,
|
||||
"writer failed: {}",
|
||||
metrics.snapshot()
|
||||
);
|
||||
let records = timing_records(root.path());
|
||||
let requests: Vec<_> = records
|
||||
.iter()
|
||||
.filter(|row| row["recordType"] == "direct.codex.request_timing")
|
||||
.collect();
|
||||
assert_eq!(requests.len(), 2);
|
||||
assert_eq!(requests[0]["transportStatus"], "stream-error");
|
||||
assert_eq!(requests[1]["transportStatus"], "dropped");
|
||||
assert!(requests
|
||||
.iter()
|
||||
.all(|row| row["firstBodyChunkOffsetMs"].is_null()));
|
||||
assert_eq!(
|
||||
metrics.snapshot()["categories"]["http-request"]["activeCount"],
|
||||
0
|
||||
stream.next().await.unwrap().unwrap_err().to_string(),
|
||||
"provider response stream failed"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn loopback_timing_keeps_original_scope_and_does_not_invent_sse_for_json() {
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
let metrics =
|
||||
super::super::DirectTurnMetrics::new(timing_log_path(root.path()), "turn-proxy");
|
||||
let calls = Arc::new(AtomicUsize::new(0));
|
||||
let listener = tokio::net::TcpListener::bind((std::net::Ipv4Addr::LOCALHOST, 0))
|
||||
.await
|
||||
.unwrap();
|
||||
let address = listener.local_addr().unwrap();
|
||||
let app = Router::new()
|
||||
.route("/responses", post(fake_upstream))
|
||||
.with_state(calls);
|
||||
let task = tokio::spawn(async move {
|
||||
let _ = axum::serve(listener, app).await;
|
||||
});
|
||||
let proxy =
|
||||
start_codex_provider_proxy(&format!("http://{address}"), "fixture-provider-key", false)
|
||||
.await
|
||||
.unwrap();
|
||||
let first = metrics.attempt("gpt-5.6-sol", "gpt-5.6-sol", "high");
|
||||
let binding = proxy.bind_metrics(first.clone());
|
||||
let response = reqwest::Client::new()
|
||||
.post(format!("{}/responses", proxy.base_url()))
|
||||
.bearer_auth(proxy.downstream_bearer_token())
|
||||
.body(r#"{"model":"gpt-5.6-sol","input":"keep secret"}"#)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
let second = metrics.attempt("gpt-5.6-sol", "gpt-5.6-sol", "high");
|
||||
let _second_binding = proxy.bind_metrics(second.clone());
|
||||
drop(binding);
|
||||
assert_eq!(
|
||||
proxy.metrics_scope.lock().unwrap().as_ref().unwrap().id(),
|
||||
second.id()
|
||||
);
|
||||
assert!(response.text().await.unwrap().contains("keep secret"));
|
||||
assert!(
|
||||
metrics.wait_for_test_writes().await,
|
||||
"writer failed: {}",
|
||||
metrics.snapshot()
|
||||
);
|
||||
let records = timing_records(root.path());
|
||||
let request = records
|
||||
.iter()
|
||||
.find(|row| row["recordType"] == "direct.codex.request_timing")
|
||||
.unwrap();
|
||||
assert_eq!(request["attemptId"], first.id());
|
||||
assert_eq!(request["transportStatus"], "eof");
|
||||
assert!(request["firstSseEventOffsetMs"].is_null());
|
||||
assert!(request["firstContentDeltaOffsetMs"].is_null());
|
||||
assert!(!serde_json::to_string(&records)
|
||||
.unwrap()
|
||||
.contains("keep secret"));
|
||||
task.abort();
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ModelFixture {
|
||||
status: StatusCode,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4816,7 +4816,6 @@ pub(crate) async fn run_direct_game_creator_turn_at_with_creation_type(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -4826,7 +4825,6 @@ async fn run_direct_game_creator_turn_at_with_creation_type_and_emitter(
|
||||
prompt: &str,
|
||||
creation_type: Option<&str>,
|
||||
turn_emitter: Option<&DirectGameCreatorTurnUpdateEmitter>,
|
||||
audit: Option<&mut DirectCodexTurnAudit>,
|
||||
direct_user_item: Option<serde_json::Value>,
|
||||
capture: Option<(
|
||||
crate::analytics::contract::Context,
|
||||
@@ -4853,7 +4851,6 @@ async fn run_direct_game_creator_turn_at_with_creation_type_and_emitter(
|
||||
prompt,
|
||||
creation_type,
|
||||
turn_emitter,
|
||||
audit,
|
||||
direct_user_item,
|
||||
capture,
|
||||
analytics_attempt_id,
|
||||
@@ -5055,7 +5052,6 @@ async fn run_direct_game_creator_turn_inner(
|
||||
prompt: &str,
|
||||
creation_type: Option<&str>,
|
||||
turn_emitter: Option<&DirectGameCreatorTurnUpdateEmitter>,
|
||||
audit: Option<&mut DirectCodexTurnAudit>,
|
||||
direct_user_item: Option<serde_json::Value>,
|
||||
capture: Option<(
|
||||
crate::analytics::contract::Context,
|
||||
@@ -5284,7 +5280,6 @@ async fn run_direct_game_creator_turn_inner(
|
||||
};
|
||||
let mut feedback_prompt = prompt.to_string();
|
||||
let mut turn_kind = DirectCodexTurnKind::User;
|
||||
let mut audit = audit;
|
||||
let mut attempt = 1;
|
||||
let reply_result = loop {
|
||||
let result = direct_game_creator_codex_chat_at_with_optional_observer(
|
||||
@@ -5294,7 +5289,6 @@ async fn run_direct_game_creator_turn_inner(
|
||||
turn_kind,
|
||||
Some(&client_turn_id),
|
||||
Some(&mut observer),
|
||||
audit.as_deref_mut(),
|
||||
Some(direct_user_item.clone()),
|
||||
)
|
||||
.await;
|
||||
@@ -5343,7 +5337,6 @@ async fn run_direct_game_creator_turn_inner(
|
||||
} else {
|
||||
let mut feedback_prompt = prompt.to_string();
|
||||
let mut turn_kind = DirectCodexTurnKind::User;
|
||||
let mut audit = audit;
|
||||
let mut response = None;
|
||||
let mut attempt = 1;
|
||||
loop {
|
||||
@@ -5354,7 +5347,6 @@ async fn run_direct_game_creator_turn_inner(
|
||||
turn_kind,
|
||||
None,
|
||||
None,
|
||||
audit.as_deref_mut(),
|
||||
Some(direct_user_item.clone()),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -61,8 +61,6 @@ pub(crate) async fn chat_with_game_creator_direct_codex(
|
||||
&user_prompt,
|
||||
creation_type.as_deref(),
|
||||
Some(&turn_emitter),
|
||||
// DirectProject 的完整回合权威已经落在 project.jsonl;不再创建平行审计日志。
|
||||
None,
|
||||
canonical_user_item,
|
||||
capture,
|
||||
analytics_attempt_id.as_deref(),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4780,27 +4780,6 @@ pub(crate) fn append_jsonl_line(path: &Path, line: &str, error_label: &str) -> R
|
||||
append_jsonl_line_unlocked(path, line, error_label)
|
||||
}
|
||||
|
||||
/// Append already serialized JSON lines under one existing append lock and fsync.
|
||||
/// Keep each record byte-for-byte intact; physical newlines belong to this framing layer.
|
||||
pub(crate) fn append_jsonl_lines(
|
||||
path: &Path,
|
||||
lines: &[&str],
|
||||
error_label: &str,
|
||||
) -> Result<(), String> {
|
||||
if lines.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
if lines
|
||||
.iter()
|
||||
.any(|line| line.is_empty() || line.contains('\n') || line.contains('\r'))
|
||||
{
|
||||
return Err(format!("{error_label}批量记录必须是非空单行 JSON"));
|
||||
}
|
||||
// Reuse all secure-open, path/handle verification, tail repair, and durability
|
||||
// checks. append_jsonl_line adds the final newline for the last record.
|
||||
append_jsonl_line(path, &lines.join("\n"), error_label)
|
||||
}
|
||||
|
||||
fn agent_db_has_conversation_message_audit_unlocked(
|
||||
file: &mut File,
|
||||
path: &Path,
|
||||
|
||||
@@ -913,9 +913,9 @@ Godot 编辑器操控复用既有 AGC 插件宿主、EditorAdapter、Runner 和
|
||||
## DirectProject 平行审计与请求分段计时退役边界(2026-09-23 核准)
|
||||
|
||||
- 当前合同:完整用户与 Codex 完成 item 保存在 `.agent/conversations/project.jsonl`;GUI 回合不再创建 `runtime/direct-codex/turns/<id>.jsonl` 或对应 `agent.db` 的 `direct.codex.turn` 摘要。旧 `offeredRead` / `firstDesign` 和请求分段计时不再属于生产保证,旧审计专题仅作历史追溯。
|
||||
- 代码依据:`agent/direct_runtime/user_input.rs` 明确传入 `audit: None`;`DirectCodexTurnAudit::start` 仅有测试调用,`DirectTurnMetrics` 和 Provider proxy 的 timing scope 随之不在生产构造。Provider proxy 本体及独立 model-usage observer 仍有现役用途,不能随旧计时链退役。
|
||||
- 实现边界:旧 `DirectCodexTurnAudit`、`DirectTurnMetrics`、可选审计 / 计时参数及专用批量 JSONL 追加包装已删除。Provider proxy 本体及独立 model-usage observer 仍有现役用途,不能随旧计时链退役;字节流透传和上游错误传递继续由现有测试验证。
|
||||
- 保留边界:运行中的界面对话 / 工具耗时、`.agent/model-usage.jsonl`、产品埋点及 Runtime Agent 审计保持各自合同。`project.jsonl` 的完成 item 写入时间不等于 turn 起止或请求阶段计时;不据此补造旧历史耗时。本次不清理或迁移用户项目内的旧审计文件。
|
||||
- 维护依据:AGC 实施计划“Direct 历史、审计与耗时的现行边界”和“DirectProject Codex 原始历史与异常恢复”。残留旧 writer 与测试按未使用代码范围清理,不能以原审计方案为由永久保留或恢复。
|
||||
- 维护依据:AGC 实施计划“Direct 历史、审计与耗时的现行边界”和“DirectProject Codex 原始历史与异常恢复”。不能以原审计方案或已退役测试为由恢复旧 writer。
|
||||
|
||||
## 2026-08-31 Direct 本轮附件只映射路径,不灌正文、不区别 GDD
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
## 1. 基线与范围
|
||||
|
||||
首次诊断的基线提交为 `016356e509f11a1a638ce45ed51b9e40ef3e36a2`,诊断时工作树干净。环境为 Windows x64、Rust `1.98.1`、Node `v24.15.0`、npm `12.0.2`。第 2~6 节和附录保留首次诊断快照;后续处理状态及契约核查见第 7~8 节,不能将附录的全部条目视为仍未解决。
|
||||
首次诊断的基线提交为 `016356e509f11a1a638ce45ed51b9e40ef3e36a2`,诊断时工作树干净。环境为 Windows x64、Rust `1.98.1`、Node `v24.15.0`、npm `12.0.2`。第 2~6 节和附录保留首次诊断快照;后续处理状态及契约核查见第 7~9 节,不能将附录的全部条目视为仍未解决。
|
||||
|
||||
AGC Rust 使用默认 features、dev profile;237 是本轮编译器诊断数,不代表 237 个独立根因,也不是所有平台、features 和 test targets 的总数。另有 5 条不带常规源码 span 的 ts-rs 宏提示,不计入 237。
|
||||
|
||||
@@ -143,7 +143,8 @@ three、FBX/GLTF loader、OrbitControls 已动态 import。不能把“改为动
|
||||
### 剩余工作
|
||||
|
||||
- [x] 第一批:清理确认不改变行为的低风险项,修复预览部署器嵌套 npm 及移动 smoke 的 Windows 调用。
|
||||
- [x] 对当前 158 条 dead_code 和 27 条 unused_imports 完成调用、构建条件及保留契约的静态初审,结论见第 8 节;尚未执行第二批删除。
|
||||
- [x] 对清理前 158 条 dead_code 和 27 条 unused_imports 完成调用、构建条件及保留契约的静态初审,结论快照见第 8 节。
|
||||
- [x] 第二批首组:清理已明确退役的 Direct 审计 / 计时链 12 条诊断,结果见第 9 节。
|
||||
- [ ] 单独核查保留的兼容重导出、身份/锁/门禁参数及 Direct 重试回合初始化,确认合同后再修改。
|
||||
- [ ] 第二批:对当前 158 条 dead_code(首次快照为 165 条)核对测试、正式 features、平台及退役合同,逐项确定保留、条件编译或删除;涉及退役范围扩大时先补方案。
|
||||
- [ ] 第三批:核实 5 条 ts-rs 提示的源类型及 TS 输出,保留现有反序列化约束。
|
||||
@@ -199,6 +200,26 @@ three、FBX/GLTF loader、OrbitControls 已动态 import。不能把“改为动
|
||||
|
||||
本次仅补充核查文档,未删除或修改业务代码。实际执行 AGC 默认 Windows dev 编译;未重跑 Rust 单元测试、正式 editor features、Linux/macOS 编译或 GUI 端到端验收。后续实施按改动范围补定向验证。
|
||||
|
||||
## 9. Direct 审计 / 计时链清理(2026-09-23)
|
||||
|
||||
契约文档提交 `5a86a64e4` 后,删除 `direct_codex_audit.rs`、`direct_turn_metrics.rs`、专属批量 JSONL 追加包装,以及 Direct Runtime / app-server / Provider proxy 中的审计和计时参数、作用域与观察分支。退役模块的专属测试一并删除;原代理测试保留分块 SSE 字节透传和上游流错误传递断言,独立模型使用记录测试继续维护。
|
||||
|
||||
未改动现役 Provider 代理的鉴权 / 路由、模型使用记录、`project.jsonl`、GUI 回合与工具事件、付费 / 资产审计或用户项目内已有文件。
|
||||
|
||||
默认 Windows dev `cargo check --locked --offline` 通过,**195 → 183 条**,没有新增诊断:
|
||||
|
||||
| 类别 | 清理前 | 清理后 |
|
||||
| --- | ---: | ---: |
|
||||
| `dead_code` | 158 | 146 |
|
||||
| `unused_imports` | 27 | 27 |
|
||||
| `unused_variables` | 9 | 9 |
|
||||
| `unused_assignments` | 1 | 1 |
|
||||
| **合计** | **195** | **183** |
|
||||
|
||||
消除的是首次清单 W023~W025、W041~W048、W134,共 12 条;5 条 ts-rs 提示不在本次范围。第 8 节保留清理前的静态分类,其中 Direct 审计 / 计时 12 条已完成代码清理,其余候选仍按实际调用与测试范围推进。
|
||||
|
||||
验证:默认编译、修改文件 rustfmt、编码、文档索引和 diff 检查通过;独立只读审查未发现参数错位或现役链路回归。**41 个定向 Rust 测试通过**:`codex_provider_proxy::` 14 个(含鉴权、主站路由标记、并行、透传与模型记录),`direct_project_history::` 22 个,app-server 的 Direct 输入 / 宿主继续和事件投影 5 个。先通过 `cargo test --locked --offline` 构建并执行代理测试,再直接复用同一测试二进制执行后两组,均单线程运行。Unix 条件下的 fake app-server 协议用例未在 Windows 执行;未运行正式 editor features、Linux/macOS 编译、真实 Provider、GUI 或安装包验收。
|
||||
|
||||
## 附录:237 条编译器诊断位置
|
||||
|
||||
以下是诊断的人工可读整理,不保存原始构建日志、本机路径或 target 缓存路径。位置相对 `apps/ai-game-creator-shell/src-tauri/`;行号只对应本节基线,修改后以符号搜索及重新编译为准。每条保留一个主要 span,编号用于本清单内跟踪,不表示独立业务缺陷。生成产物项须回到 `build_support/runtime_prompt_bundle.rs` 处理。
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
### Direct 历史、审计与耗时的现行边界(2026-09-23 核准)
|
||||
|
||||
- DirectProject 的完整回合条目只写入 `.agent/conversations/project.jsonl`,按 [原始历史与异常恢复](./【技术方案】DirectProject%20Codex原始历史与异常恢复-2026-09-04.md) 保存 canonical 用户条目和 Codex 完成的原始 item。工具调用与结果从同一事实源读取,前端继续使用安全投影;完整私有历史不能直接作为埋点或上传报告。
|
||||
- GUI 回合不再创建 `.agent/runtime/direct-codex/turns/<clientTurnId>.jsonl` 平行审计日志,也不再由该链写入 `agent.db` 的 `direct.codex.turn` 摘要。`direct_runtime/user_input.rs` 明确向回合执行器传入 `audit: None`;旧 `DirectCodexTurnAudit`、`DirectTurnMetrics` 构造和配套测试不构成现役行为承诺。旧审计专题归入历史,不要求恢复其 writer、`offeredRead` / `firstDesign` 投影或分段计时落盘。
|
||||
- GUI 回合不再创建 `.agent/runtime/direct-codex/turns/<clientTurnId>.jsonl` 平行审计日志,也不再由该链写入 `agent.db` 的 `direct.codex.turn` 摘要。旧 `DirectCodexTurnAudit`、`DirectTurnMetrics`、沿途审计 / 计时参数、专用批量 JSONL 追加包装和退役测试已删除。旧审计专题归入历史,不要求恢复其 writer、`offeredRead` / `firstDesign` 投影或分段计时落盘;代理的字节流透传、错误传递和独立模型记录测试继续维护。
|
||||
- 会话运行中的对话和工具界面耗时仍按生命周期事件显示;模型请求 / 响应身份仍由 `.agent/model-usage.jsonl` 独立记录,Provider proxy 本体继续承担路由与响应型号观察。两者都不能证明 HTTP 首包、首 SSE、首内容 delta 或各阶段占用已形成生产分段计时记录;旧计时 fixture 通过也不能作为生产接入证据。`project.jsonl` 的 `recordedAt` 只是完成 item 的写入时间,不是 turn 起止时间;重进历史缺终态边界时不能承诺精确耗时,也不得补造请求阶段统计。
|
||||
- 历史项目可能留有旧审计文件或摘要;本次契约收敛不删除、迁移或重写用户数据,也不要求新回合继续追加。现役模型使用记录、产品埋点、Runtime Agent 审计和付费 / 恢复凭证各守原有合同,不因 Direct 平行日志停用而退役。
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
DirectProject 只使用 `.agent/conversations/project.jsonl` 作为对话历史。历史保存 Codex Responses API 的完整 item,使聊天展示与新线程恢复使用同一份事实来源;两者只是不同读取动作。
|
||||
|
||||
本方案只适用于 DirectProject,不改变 Agent session 历史。DirectProject 已停用 `runtime/direct-codex/turns` 平行审计账本,GUI 入口不再构造旧审计对象;完整回合条目统一来自本方案的 `project.jsonl`。旧文件不作为新回合必需产物,也不因本次契约更新被删除或迁移。
|
||||
本方案只适用于 DirectProject,不改变 Agent session 历史。DirectProject 已退役 `runtime/direct-codex/turns` 平行审计账本并删除旧审计 / 计时实现;完整回合条目统一来自本方案的 `project.jsonl`。用户项目中的旧日志不作为新回合必需产物,也不因代码清理被删除或迁移。
|
||||
|
||||
## 文件格式
|
||||
|
||||
|
||||
@@ -323,7 +323,7 @@ chat_with_game_creator_direct_codex
|
||||
|
||||
## 9. 代码落地
|
||||
|
||||
新增 [`apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_audit.rs`](../../apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_audit.rs):
|
||||
原方案新增 `apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_audit.rs`(现已删除,以下仅作历史追溯):
|
||||
|
||||
- `DirectCodexTurnAudit`
|
||||
- `start` / `observe_item` / `finish`
|
||||
|
||||
@@ -473,7 +473,7 @@ session.json 是本地恢复元数据,不是待上传事件;事件文件不
|
||||
- 生命周期与配置:`apps/ai-game-creator-shell/src-tauri/src/main.rs`、`config.rs`、`platform_session.rs`。
|
||||
- 项目创建与打开:`apps/ai-game-creator-shell/src-tauri/src/commands.rs`、`src/features/app-shell/useHomeProjectCreation.ts`;离开登记由 `WorkspaceLauncher.tsx` 保持。
|
||||
- Direct 前端尝试与终态确认:`apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectProjectChatController.ts`;沿用 `services/clientAnalytics.ts` 冻结账号代次、每次原生重试生成 attempt ID、只确认最后一次尝试。不在已退役的 App 聊天状态链恢复接线。
|
||||
- Direct 执行与审计:`apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/user_input.rs`、`agent/direct_runtime/mod.rs`、`agent/direct_codex_audit.rs`。
|
||||
- Direct 执行与现役历史:`apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/user_input.rs`、`agent/direct_runtime/mod.rs`、`agent/direct_project_history.rs`;旧 `direct_codex_audit.rs` 已删除,不作为埋点接入点。
|
||||
- Design 执行与持久化:`apps/ai-game-creator-shell/src-tauri/src/agent/design_runtime.rs`、`agent/runtime_protocol/design_session.rs`、`agent/design_tools.rs`。
|
||||
- revision:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/project_gates.rs`,结合各实际写入调用方。
|
||||
- 预览与保存:`apps/ai-game-creator-shell/src-tauri/src/preview.rs`、`ui_editor/persistence.rs`、`project/checkpoint.rs`。
|
||||
|
||||
Reference in New Issue
Block a user