mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-07 00:05:49 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eff522f2b6 | ||
|
|
65a175d48c | ||
|
|
f1872df293 |
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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!({
|
||||
|
||||
@@ -2,7 +2,6 @@ import request from '../request'
|
||||
import { API_BASE_URL } from '../constants'
|
||||
|
||||
export const SYSTEM_UPDATE_EVENTS_URL = `${API_BASE_URL}/api/admin/system/update-events`
|
||||
const SYSTEM_HEALTH_URL = `${API_BASE_URL}/healthz`
|
||||
|
||||
export interface SystemVersion {
|
||||
version: string
|
||||
@@ -47,20 +46,14 @@ export interface SystemOperationStarted {
|
||||
needRestart?: boolean
|
||||
}
|
||||
|
||||
export function getSystemVersion() {
|
||||
export function getSystemVersion(options: { timeoutMs?: number } = {}) {
|
||||
return request<SystemVersion>({
|
||||
url: '/api/admin/system/version',
|
||||
method: 'GET',
|
||||
...(options.timeoutMs ? { timeout: options.timeoutMs } : {}),
|
||||
})
|
||||
}
|
||||
|
||||
export async function checkSystemHealth() {
|
||||
const response = await fetch(SYSTEM_HEALTH_URL, { cache: 'no-store' })
|
||||
if (!response.ok) {
|
||||
throw new Error('service is not ready')
|
||||
}
|
||||
}
|
||||
|
||||
export function getSystemUpdateDetail(refresh = false) {
|
||||
return request<SystemUpdateInfo>({
|
||||
url: '/api/admin/system/update-detail',
|
||||
|
||||
@@ -2,7 +2,6 @@ import { computed, ref, shallowRef } from 'vue'
|
||||
|
||||
import {
|
||||
SYSTEM_UPDATE_EVENTS_URL,
|
||||
checkSystemHealth,
|
||||
getSystemVersion,
|
||||
getSystemUpdateDetail,
|
||||
performSystemUpdate,
|
||||
@@ -38,14 +37,14 @@ const updateLogs = ref<SystemUpdateLog[]>([])
|
||||
const updateStreaming = shallowRef(false)
|
||||
const updateStreamError = shallowRef('')
|
||||
const updateEventSource = shallowRef<EventSource | null>(null)
|
||||
const restartTargetVersion = shallowRef('')
|
||||
|
||||
let loadVersionPromise: Promise<SystemVersion> | undefined
|
||||
let loadSystemPromise: Promise<void> | undefined
|
||||
const maxUpdateLogs = 200
|
||||
const restartStopPollAttempts = 50
|
||||
const restartStopPollIntervalMs = 200
|
||||
const restartReadyPollAttempts = 60
|
||||
const restartReadyPollIntervalMs = 1000
|
||||
const restartReadyTimeoutMs = 60_000
|
||||
const restartProbeTimeoutMs = 2_000
|
||||
const restartReadyPollIntervalMs = 500
|
||||
|
||||
const hasUpdate = computed(() => Boolean(updateInfo.value?.hasUpdate ?? version.value?.hasUpdate))
|
||||
const isReleaseBuild = computed(() => updateInfo.value?.buildType === 'release')
|
||||
@@ -63,6 +62,7 @@ function resetUpdateResult() {
|
||||
updateError.value = ''
|
||||
updateSuccess.value = false
|
||||
needRestart.value = false
|
||||
restartTargetVersion.value = ''
|
||||
}
|
||||
|
||||
function isUpdateLog(value: unknown): value is SystemUpdateLog {
|
||||
@@ -206,6 +206,7 @@ async function updateNow(targetVersion: string) {
|
||||
const result = await performSystemUpdate(confirmedTargetVersion)
|
||||
updateSuccess.value = true
|
||||
needRestart.value = result.needRestart
|
||||
restartTargetVersion.value = result.needRestart ? normalizeVersion(result.targetVersion) : ''
|
||||
updateInfo.value = {
|
||||
...currentInfo,
|
||||
latestVersion: result.targetVersion,
|
||||
@@ -233,39 +234,36 @@ function delay(ms: number) {
|
||||
}
|
||||
|
||||
async function waitForServiceAndReload() {
|
||||
let stopped = false
|
||||
for (let attempt = 0; attempt < restartStopPollAttempts; attempt += 1) {
|
||||
try {
|
||||
await checkSystemHealth()
|
||||
} catch {
|
||||
stopped = true
|
||||
break
|
||||
}
|
||||
await delay(restartStopPollIntervalMs)
|
||||
}
|
||||
const expectedVersion = restartTargetVersion.value
|
||||
const deadline = Date.now() + restartReadyTimeoutMs
|
||||
|
||||
if (!stopped) {
|
||||
updateError.value = '未检测到服务停止,请检查重启进程状态'
|
||||
restarting.value = false
|
||||
return
|
||||
}
|
||||
|
||||
for (let attempt = 0; attempt < restartReadyPollAttempts; attempt += 1) {
|
||||
while (Date.now() < deadline) {
|
||||
try {
|
||||
await checkSystemHealth()
|
||||
window.location.reload()
|
||||
return
|
||||
} catch {
|
||||
if (attempt < restartReadyPollAttempts - 1) {
|
||||
await delay(restartReadyPollIntervalMs)
|
||||
const readyVersion = await getSystemVersion({ timeoutMs: restartProbeTimeoutMs })
|
||||
if (normalizeVersion(readyVersion.version) === expectedVersion) {
|
||||
window.location.reload()
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
// 进程切换期间短暂不可达,继续等待目标版本就绪
|
||||
}
|
||||
|
||||
const remainingMs = deadline - Date.now()
|
||||
if (remainingMs > 0) {
|
||||
await delay(Math.min(restartReadyPollIntervalMs, remainingMs))
|
||||
}
|
||||
}
|
||||
|
||||
updateError.value = '服务重启恢复超时,请检查进程是否已被自重启或外部守护进程拉起'
|
||||
updateError.value = `服务未在预期时间内启动 v${expectedVersion}`
|
||||
restarting.value = false
|
||||
}
|
||||
|
||||
function normalizeVersion(value: unknown) {
|
||||
return String(value ?? '')
|
||||
.trim()
|
||||
.replace(/^v/i, '')
|
||||
}
|
||||
|
||||
function apiErrorStatus(error: unknown) {
|
||||
return error instanceof ApiError ? error.status : 0
|
||||
}
|
||||
@@ -273,6 +271,12 @@ function apiErrorStatus(error: unknown) {
|
||||
async function restartNow() {
|
||||
if (restarting.value) return
|
||||
|
||||
if (!restartTargetVersion.value) {
|
||||
const error = new Error('缺少待生效的目标版本')
|
||||
updateError.value = error.message
|
||||
throw error
|
||||
}
|
||||
|
||||
restarting.value = true
|
||||
updateError.value = ''
|
||||
disconnectUpdateEvents()
|
||||
|
||||
@@ -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>
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 }">
|
||||
|
||||
@@ -1 +1 @@
|
||||
version: "2.2.0"
|
||||
version: "2.2.1"
|
||||
|
||||
Reference in New Issue
Block a user