fix: surface Codex ultra reasoning usage

Restore the official Ultra preset from the proactive multi-agent marker while preserving the max effort sent upstream. Show subagent requests with an inline icon in both usage tables and keep their wire effort visible.

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
zyycn
2026-07-13 08:47:25 +08:00
co-authored by Codex
parent f1872df293
commit 65a175d48c
13 changed files with 255 additions and 20 deletions
@@ -102,6 +102,7 @@ struct DashboardUsageRecordData {
client_ip: Option<String>,
user_agent: Option<String>,
reasoning_effort: Option<String>,
reasoning_preset: Option<String>,
compact: bool,
request_kind: Option<String>,
subagent_kind: Option<String>,
@@ -517,6 +518,7 @@ fn dashboard_usage_record_data(
let user_agent = metadata_string(&record.metadata, &["userAgent", "user_agent"]);
let reasoning_effort =
metadata_string(&record.metadata, &["reasoningEffort", "reasoning_effort"]);
let reasoning_preset = metadata_string(&record.metadata, &["reasoningPreset"]);
let compact = record
.metadata
.get("compact")
@@ -541,6 +543,7 @@ fn dashboard_usage_record_data(
client_ip,
user_agent,
reasoning_effort,
reasoning_preset,
compact,
request_kind,
subagent_kind,
+3
View File
@@ -88,6 +88,7 @@ pub(crate) struct UsageRecordData {
client_ip: Option<String>,
user_agent: Option<String>,
reasoning_effort: Option<String>,
reasoning_preset: Option<String>,
compact: bool,
request_kind: Option<String>,
subagent_kind: Option<String>,
@@ -442,6 +443,7 @@ fn usage_record_data(
let user_agent = metadata_string(&record.metadata, &["userAgent", "user_agent"]);
let reasoning_effort =
metadata_string(&record.metadata, &["reasoningEffort", "reasoning_effort"]);
let reasoning_preset = metadata_string(&record.metadata, &["reasoningPreset"]);
let compact = record
.metadata
.get("compact")
@@ -463,6 +465,7 @@ fn usage_record_data(
client_ip,
user_agent,
reasoning_effort,
reasoning_preset,
compact,
request_kind,
subagent_kind,
+1
View File
@@ -424,6 +424,7 @@ fn response_request_summary(
"compact": semantics.compact,
"requestKind": semantics.request_kind,
"subagentKind": semantics.subagent_kind,
"reasoningPreset": semantics.reasoning_preset,
"transport": backend_transport_name(transport),
"inputType": json_value_kind(input),
"inputItemsCount": input.and_then(Value::as_array).map(Vec::len),
+1
View File
@@ -439,6 +439,7 @@ pub(crate) fn enrich_response_request_semantics(
object.insert("compact".to_string(), Value::Bool(semantics.compact));
insert_trimmed_string(object, "requestKind", semantics.request_kind.as_deref());
insert_trimmed_string(object, "subagentKind", semantics.subagent_kind.as_deref());
insert_trimmed_string(object, "reasoningPreset", semantics.reasoning_preset);
}
pub(crate) fn reasoning_effort_from_request(request: &CodexResponsesRequest) -> Option<&str> {
@@ -11,6 +11,10 @@ use crate::{
},
};
const MULTI_AGENT_MODE_OPEN_TAG: &str = "<multi_agent_mode>";
const MULTI_AGENT_MODE_CLOSE_TAG: &str = "</multi_agent_mode>";
const PROACTIVE_MULTI_AGENT_MODE_PREFIX: &str = "Proactive multi-agent delegation is active.";
/// Codex Responses 上游请求体。
///
/// 发往上游的 Responses 请求。`body` 持有客户端原始 JSON object,逐字段(含顺序、
@@ -97,6 +101,8 @@ pub struct CodexRequestSemantics {
pub request_kind: Option<String>,
/// Codex turn metadata 中的子代理类型。
pub subagent_kind: Option<String>,
/// Codex 客户端选择的推理预设。
pub reasoning_preset: Option<&'static str>,
/// 请求是否为远端压缩。
pub compact: bool,
}
@@ -696,7 +702,7 @@ impl CodexResponsesRequest {
self.body.get("client_metadata")
}
/// 提取 Codex 请求类型、子代理类型与压缩语义。
/// 提取 Codex 请求类型、子代理类型、推理预设与压缩语义。
pub fn semantics(&self) -> CodexRequestSemantics {
let turn_metadata = self
.turn_metadata
@@ -717,6 +723,16 @@ impl CodexResponsesRequest {
.and_then(|metadata| metadata.get("subagent_kind"))
.and_then(Value::as_str)
.and_then(non_empty_owned_string);
let reasoning_preset = (subagent_kind.is_none()
&& self
.reasoning()
.and_then(|value| value.get("effort"))
.and_then(Value::as_str)
== Some("max")
&& self
.latest_multi_agent_mode()
.is_some_and(|mode| mode.starts_with(PROACTIVE_MULTI_AGENT_MODE_PREFIX)))
.then_some("ultra");
let compact = request_kind.as_deref() == Some("compaction")
|| self
.input()
@@ -726,10 +742,19 @@ impl CodexResponsesRequest {
CodexRequestSemantics {
request_kind,
subagent_kind,
reasoning_preset,
compact,
}
}
fn latest_multi_agent_mode(&self) -> Option<&str> {
latest_multi_agent_mode(self.input()).or_else(|| {
self.local_replay_input
.as_deref()
.and_then(|input| latest_multi_agent_mode(input.as_slice()))
})
}
/// 设置 / 合并 client metadata。
pub fn set_client_metadata(&mut self, client_metadata: Option<Value>) {
match client_metadata {
@@ -772,3 +797,25 @@ fn non_empty_owned_string(value: &str) -> Option<String> {
let value = value.trim();
(!value.is_empty()).then(|| value.to_string())
}
fn latest_multi_agent_mode(input: &[Value]) -> Option<&str> {
input.iter().rev().find_map(|item| {
if item.get("role").and_then(Value::as_str) != Some("developer") {
return None;
}
item.get("content")?
.as_array()?
.iter()
.rev()
.filter_map(|content| content.get("text").and_then(Value::as_str))
.find_map(multi_agent_mode_from_text)
})
}
fn multi_agent_mode_from_text(text: &str) -> Option<&str> {
let close = text.rfind(MULTI_AGENT_MODE_CLOSE_TAG)?;
let before_close = &text[..close];
let open = before_close.rfind(MULTI_AGENT_MODE_OPEN_TAG)?;
let body = &before_close[open + MULTI_AGENT_MODE_OPEN_TAG.len()..];
Some(body.trim())
}
+9 -4
View File
@@ -403,10 +403,12 @@ async fn dashboard_summary_should_return_backend_formatted_time_fields() {
)
.await;
let record_at = Utc::now() - Duration::seconds(1);
store
.append(&usage_record_with_tokens(record_at, 10))
.await
.unwrap();
let mut record = usage_record_with_tokens(record_at, 10);
record.metadata = json!({
"reasoningEffort": "max",
"reasoningPreset": "ultra"
});
store.append(&record).await.unwrap();
let body = dashboard_summary(app).await;
@@ -414,6 +416,9 @@ async fn dashboard_summary_should_return_backend_formatted_time_fields() {
body["data"]["usageRecords"][0]["createdAtDisplay"],
china_datetime(&record_at)
);
assert_eq!(body["data"]["usageRecords"][0]["reasoningEffort"], "max");
assert_eq!(body["data"]["usageRecords"][0]["reasoningPreset"], "ultra");
assert!(body["data"]["usageRecords"][0]["subagentKind"].is_null());
assert!(body["data"]["usageRecords"][0].get("metadata").is_none());
}
+24 -4
View File
@@ -46,10 +46,18 @@ async fn usage_route_returns_success_facts_without_legacy_level() {
record.metadata = json!({
"compact": true,
"requestKind": "compaction",
"reasoningEffort": "max",
"reasoningPreset": "ultra"
});
usage.append(&record).await.unwrap();
let mut subagent = success_record("subagent completed");
subagent.id = "usage_route_subagent".to_string();
subagent.client_api_key_id = Some("key_42".to_string());
subagent.metadata = json!({
"subagentKind": "thread_spawn",
"reasoningEffort": "max"
});
usage.append(&record).await.unwrap();
usage.append(&subagent).await.unwrap();
let response = app
.oneshot(
@@ -65,16 +73,28 @@ async fn usage_route_returns_success_facts_without_legacy_level() {
let body = response_json(response).await;
assert_eq!(body["data"]["page"]["page"], 1);
assert_eq!(body["data"]["page"]["pageSize"], 50);
assert_eq!(body["data"]["page"]["total"], 1);
assert_eq!(body["data"]["page"]["total"], 2);
assert!(body["data"]["page"].get("nextCursor").is_none());
let item = &body["data"]["items"][0];
let items = body["data"]["items"].as_array().unwrap();
let item = items
.iter()
.find(|item| item["id"] == "usage_route_success")
.unwrap();
assert_eq!(item["id"], "usage_route_success");
assert_eq!(item["clientApiKeyId"], "key_42");
assert_eq!(item["statusCode"], 200);
assert_eq!(item["compact"], true);
assert_eq!(item["requestKind"], "compaction");
assert_eq!(item["subagentKind"], "thread_spawn");
assert!(item["subagentKind"].is_null());
assert_eq!(item["reasoningEffort"], "max");
assert_eq!(item["reasoningPreset"], "ultra");
let subagent = items
.iter()
.find(|item| item["id"] == "usage_route_subagent")
.unwrap();
assert_eq!(subagent["subagentKind"], "thread_spawn");
assert_eq!(subagent["reasoningEffort"], "max");
assert!(subagent["reasoningPreset"].is_null());
assert!(item.get("level").is_none());
}
@@ -138,7 +138,15 @@ async fn responses_should_use_imported_account_record_usage_cookie_and_usage_rec
.body(Body::from(
json!({
"model": "gpt-5.5",
"input": [],
"reasoning": {"effort": "max"},
"input": [{
"type": "message",
"role": "developer",
"content": [{
"type": "input_text",
"text": "<multi_agent_mode>Proactive multi-agent delegation is active.</multi_agent_mode>"
}]
}],
"stream": false,
"use_websocket": false
})
@@ -177,8 +185,9 @@ async fn responses_should_use_imported_account_record_usage_cookie_and_usage_rec
assert_eq!(event.account_id.as_deref(), Some("acct_chat"));
assert_eq!(event.status_code, Some(200));
assert_eq!(event.response_id.as_deref(), Some("resp_usage"));
assert_eq!(event.response_id.as_deref(), Some("resp_usage"));
assert_eq!(metadata["stream"], false);
assert_eq!(metadata["reasoningEffort"], "max");
assert_eq!(metadata["reasoningPreset"], "ultra");
assert_eq!(event.input_tokens, Some(7));
}
@@ -1,3 +1,5 @@
use std::sync::Arc;
use serde_json::Value;
use super::*;
@@ -447,6 +449,120 @@ fn responses_request_semantics_should_detect_compaction_trigger_without_metadata
assert!(request.semantics().compact);
}
#[test]
fn responses_request_semantics_should_restore_ultra_from_proactive_mode() {
let body = json!({
"model": "gpt-5.6-sol",
"reasoning": {"effort": "max"},
"input": [developer_multi_agent_mode(
"Proactive multi-agent delegation is active. Use sub-agents when useful.",
)]
});
let request = build_codex_request(body.as_object().unwrap().clone(), &HeaderMap::new(), None);
assert_eq!(request.semantics().reasoning_preset, Some("ultra"));
assert_eq!(request.reasoning().unwrap()["effort"], "max");
}
#[test]
fn responses_request_semantics_should_use_latest_multi_agent_mode() {
let body = json!({
"model": "gpt-5.6-sol",
"reasoning": {"effort": "max"},
"input": [
developer_multi_agent_mode("Proactive multi-agent delegation is active."),
developer_multi_agent_mode("Do not spawn sub-agents unless explicitly requested."),
]
});
let request = build_codex_request(body.as_object().unwrap().clone(), &HeaderMap::new(), None);
assert_eq!(request.semantics().reasoning_preset, None);
}
#[test]
fn responses_request_semantics_should_ignore_user_multi_agent_mode_marker() {
let body = json!({
"model": "gpt-5.6-sol",
"reasoning": {"effort": "max"},
"input": [{
"type": "message",
"role": "user",
"content": [{
"type": "input_text",
"text": "<multi_agent_mode>Proactive multi-agent delegation is active.</multi_agent_mode>",
}],
}]
});
let request = build_codex_request(body.as_object().unwrap().clone(), &HeaderMap::new(), None);
assert_eq!(request.semantics().reasoning_preset, None);
}
#[test]
fn responses_request_semantics_should_require_max_for_ultra() {
let body = json!({
"model": "gpt-5.6-sol",
"reasoning": {"effort": "high"},
"input": [developer_multi_agent_mode(
"Proactive multi-agent delegation is active.",
)]
});
let request = build_codex_request(body.as_object().unwrap().clone(), &HeaderMap::new(), None);
assert_eq!(request.semantics().reasoning_preset, None);
}
#[test]
fn responses_request_semantics_should_not_mark_subagent_as_ultra() {
let mut headers = HeaderMap::new();
headers.insert(
"x-codex-turn-metadata",
r#"{"subagent_kind":"thread_spawn"}"#.parse().unwrap(),
);
let body = json!({
"model": "gpt-5.6-sol",
"reasoning": {"effort": "max"},
"input": [developer_multi_agent_mode(
"Proactive multi-agent delegation is active.",
)]
});
let request = build_codex_request(body.as_object().unwrap().clone(), &headers, None);
assert_eq!(request.semantics().reasoning_preset, None);
}
#[test]
fn responses_request_semantics_should_read_mode_from_local_replay_history() {
let body = json!({
"model": "gpt-5.6-sol",
"reasoning": {"effort": "max"},
"input": [{"type": "message", "role": "user", "content": []}]
});
let mut request =
build_codex_request(body.as_object().unwrap().clone(), &HeaderMap::new(), None);
request.local_replay_input = Some(Arc::new(vec![developer_multi_agent_mode(
"Proactive multi-agent delegation is active.",
)]));
assert_eq!(request.semantics().reasoning_preset, Some("ultra"));
}
fn developer_multi_agent_mode(mode: &str) -> Value {
json!({
"type": "message",
"role": "developer",
"content": [{
"type": "input_text",
"text": format!("<multi_agent_mode>{mode}</multi_agent_mode>"),
}],
})
}
#[test]
fn openai_streaming_response_with_previous_response_should_require_websocket() {
let body = json!({
@@ -6,7 +6,6 @@ import BaseTable from '@/components/base/BaseTable/index.vue'
import {
usageAccountText,
usageIsCompact,
usageReasoningEffort,
usageRecordColumns,
usageRecordType,
usageRecordTypeClass,
@@ -14,6 +13,7 @@ import {
import UsageClientIpCell from '@/views/usage/components/UsageClientIpCell.vue'
import UsageBillingCell from '@/views/usage/components/UsageBillingCell.vue'
import UsageModelCell from '@/views/usage/components/UsageModelCell.vue'
import UsageReasoningEffortCell from '@/views/usage/components/UsageReasoningEffortCell.vue'
import UsageTokenCell from '@/views/usage/components/UsageTokenCell.vue'
defineProps<{
@@ -57,9 +57,7 @@ const dashboardUsageRecordColumns = usageRecordColumns.filter((column) => column
</template>
<template #reasoningEffort="{ row }">
<span class="whitespace-nowrap text-[12px] font-[720] text-(--cp-text-primary)">
{{ usageReasoningEffort(row) }}
</span>
<UsageReasoningEffortCell :record="row" />
</template>
<template #route="{ row }">
@@ -0,0 +1,25 @@
<script setup lang="ts">
import { Bot } from '@lucide/vue'
import { usageIsSubagent, usageReasoningEffort } from '../constants'
defineProps<{
record: any
}>()
</script>
<template>
<span
class="inline-flex items-center gap-1 whitespace-nowrap text-[12px] font-[720] text-(--cp-text-primary)"
>
<span>{{ usageReasoningEffort(record) }}</span>
<span
v-if="usageIsSubagent(record)"
class="inline-flex shrink-0 text-(--cp-info-text)"
title="子代理请求"
aria-label="子代理请求"
>
<Bot class="size-3.25" stroke-width="2.2" />
</span>
</span>
</template>
+10 -1
View File
@@ -243,7 +243,16 @@ export function usageUserAgent(record: any) {
}
export function usageReasoningEffort(record: any) {
return record?.reasoningEffort || record?.metadata?.reasoningEffort || '—'
const reasoningEffort = record?.reasoningEffort || record?.metadata?.reasoningEffort || '—'
if (usageIsSubagent(record)) {
return reasoningEffort
}
return record?.reasoningPreset || record?.metadata?.reasoningPreset || reasoningEffort
}
export function usageIsSubagent(record: any) {
return Boolean(record?.subagentKind)
}
export function usageIsCompact(record: any) {
+2 -4
View File
@@ -12,7 +12,6 @@ import {
usageTimeRangeOptions,
usageAccountText,
usageIsCompact,
usageReasoningEffort,
usageRecordColumns,
usageRecordType,
usageRecordTypeClass,
@@ -27,6 +26,7 @@ import UsageInsightsGrid from './components/UsageInsightsGrid.vue'
import UsageModelCell from './components/UsageModelCell.vue'
import OpsErrorPanel from './components/OpsErrorPanel.vue'
import UsageRecordDetailModal from './components/UsageRecordDetailModal.vue'
import UsageReasoningEffortCell from './components/UsageReasoningEffortCell.vue'
import UsageSummaryCards from './components/UsageSummaryCards.vue'
import UsageTokenCell from './components/UsageTokenCell.vue'
@@ -184,9 +184,7 @@ watch(timeRange, () => {
</template>
<template #reasoningEffort="{ row }">
<span class="whitespace-nowrap text-[12px] font-[720] text-(--cp-text-primary)">
{{ usageReasoningEffort(row) }}
</span>
<UsageReasoningEffortCell :record="row" />
</template>
<template #route="{ row }">