mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-08 00:05:59 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13a9df2e07 | ||
|
|
37a5a6702b | ||
|
|
afb68e97d6 | ||
|
|
93b9741af2 | ||
|
|
013d592454 | ||
|
|
0f56e9b80c | ||
|
|
6ec5ecb9ac | ||
|
|
460d439351 |
@@ -201,6 +201,7 @@ struct RestartAcceptedView {
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct SystemUpdateEventView {
|
||||
id: String,
|
||||
operation_id: Option<String>,
|
||||
level: &'static str,
|
||||
step: Option<String>,
|
||||
@@ -404,6 +405,7 @@ where
|
||||
impl From<SystemUpdateEvent> for SystemUpdateEventView {
|
||||
fn from(event: SystemUpdateEvent) -> Self {
|
||||
Self {
|
||||
id: event.id,
|
||||
operation_id: event.operation_id,
|
||||
level: update_event_level_name(event.level),
|
||||
step: event.step,
|
||||
@@ -418,6 +420,7 @@ impl From<SystemUpdateEvent> for SystemUpdateEventView {
|
||||
impl SystemUpdateEventView {
|
||||
fn into_json(self) -> serde_json::Value {
|
||||
serde_json::json!({
|
||||
"id": self.id,
|
||||
"operationId": self.operation_id,
|
||||
"level": self.level,
|
||||
"step": self.step,
|
||||
|
||||
@@ -209,6 +209,7 @@ fn codex_model_json(profile: &PublicModelProfile, index: usize) -> Value {
|
||||
fn reasoning_effort_description(effort: &str) -> &'static str {
|
||||
match effort {
|
||||
"none" => "No reasoning",
|
||||
"minimal" => "Minimal reasoning",
|
||||
"low" => "Fast responses with lighter reasoning",
|
||||
"medium" => "Balances speed and reasoning depth for everyday tasks",
|
||||
"high" => "Greater reasoning depth for complex problems",
|
||||
|
||||
@@ -38,7 +38,7 @@ fn update_request_should_preserve_target_version_for_domain_validation() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_event_stream_should_preserve_download_progress_percent() {
|
||||
async fn update_event_stream_should_preserve_event_id_and_download_progress_percent() {
|
||||
let fixture = AdminTestFixture::with_system(Arc::new(ProgressSystem)).await;
|
||||
fixture.auth.insert_session("valid-session");
|
||||
let response = app(fixture.state())
|
||||
@@ -57,6 +57,10 @@ async fn update_event_stream_should_preserve_download_progress_percent() {
|
||||
.expect("update event body");
|
||||
let body = String::from_utf8(body.to_vec()).expect("UTF-8 event stream");
|
||||
|
||||
assert!(
|
||||
body.contains(r#""id":"update-progress-1""#),
|
||||
"unexpected update event stream: {body}"
|
||||
);
|
||||
assert!(
|
||||
body.contains(r#""progressPercent":40"#),
|
||||
"unexpected update event stream: {body}"
|
||||
|
||||
@@ -67,7 +67,9 @@ impl ExecutionService for ModelsExecution {
|
||||
)
|
||||
.with_reasoning(
|
||||
Some("medium".to_owned()),
|
||||
["low", "medium", "high"].map(str::to_owned).to_vec(),
|
||||
["low", "medium", "high", "xhigh"]
|
||||
.map(str::to_owned)
|
||||
.to_vec(),
|
||||
)
|
||||
.with_context_window_tokens(Some(500_000))
|
||||
.with_image_input(true)
|
||||
@@ -138,6 +140,27 @@ async fn models_should_encode_provider_profiles_for_current_codex_clients() {
|
||||
assert_eq!(model["context_window"], 500_000);
|
||||
assert_eq!(model["apply_patch_tool_type"], "freeform");
|
||||
assert_eq!(model["service_tiers"], serde_json::json!([]));
|
||||
assert_eq!(
|
||||
model["supported_reasoning_levels"],
|
||||
serde_json::json!([
|
||||
{
|
||||
"effort": "low",
|
||||
"description": "Fast responses with lighter reasoning"
|
||||
},
|
||||
{
|
||||
"effort": "medium",
|
||||
"description": "Balances speed and reasoning depth for everyday tasks"
|
||||
},
|
||||
{
|
||||
"effort": "high",
|
||||
"description": "Greater reasoning depth for complex problems"
|
||||
},
|
||||
{
|
||||
"effort": "xhigh",
|
||||
"description": "Extra high reasoning depth for complex problems"
|
||||
}
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -57,24 +57,25 @@ pub use transport::{
|
||||
GrokBillingTransportError, GrokBillingTransportErrorKind, GrokBillingTransportFuture,
|
||||
GrokBillingTransportResponse, GrokCanonicalDecoder, GrokCatalogApiBackend,
|
||||
GrokCatalogCapabilities, GrokCatalogCapabilityEvidence, GrokCatalogLimits, GrokCatalogMetadata,
|
||||
GrokCatalogModel, GrokClientIdentity, GrokCompactionDecodeError, GrokCompactionRequest,
|
||||
GrokCompactionSummaryDecoder, GrokCredentialFailure, GrokCredentialFeedbackFuture,
|
||||
GrokDnsResolutionError, GrokDnsResolutionPlan, GrokDnsResolutionPolicy, GrokEndpointPolicy,
|
||||
GrokHeader, GrokHeaderValue, GrokInferenceChunkStream, GrokInferenceClientCacheStatus,
|
||||
GrokInferenceDnsObservation, GrokInferenceDnsSource, GrokInferenceRequest,
|
||||
GrokInferenceResponse, GrokInferenceTransport, GrokInferenceTransportError,
|
||||
GrokInferenceTransportErrorKind, GrokInferenceTransportFuture, GrokInferenceTransportMetrics,
|
||||
GrokModelCatalogClient, GrokModelCatalogError, GrokModelCatalogRequest,
|
||||
GrokModelCatalogSession, GrokModelCatalogSessionError, GrokModelCatalogSnapshot,
|
||||
GrokModelCatalogTransport, GrokModelCatalogTransportError, GrokModelCatalogTransportErrorKind,
|
||||
GrokModelCatalogTransportFuture, GrokModelCatalogTransportResponse, GrokProviderConfigError,
|
||||
GrokProviderTransport, GrokRequestEncodeError, GrokReqwestTransportBuildError,
|
||||
GrokResponsesRequest, GrokSessionAffinityKey, GrokSessionBinding, GrokSessionDataError,
|
||||
GrokSessionLeaseGuard, GrokSessionSelection, GrokSessionSelector, GrokSessionSelectorError,
|
||||
GrokSessionSelectorFuture, MAX_GROK_BILLING_BYTES, MAX_GROK_MODEL_CATALOG_BYTES,
|
||||
OfficialGrokEndpointPolicy, ReqwestGrokInferenceTransport, ReqwestGrokModelCatalogTransport,
|
||||
ReqwestOAuthTransport, SelectedGrokSession, XAI_PROVIDER_NAME, build_grok_headers,
|
||||
grok_billing_breakdown, parse_grok_billing, parse_grok_model_catalog,
|
||||
GrokCatalogModel, GrokCatalogReasoningEffort, GrokClientIdentity, GrokCompactionDecodeError,
|
||||
GrokCompactionRequest, GrokCompactionSummaryDecoder, GrokCredentialFailure,
|
||||
GrokCredentialFeedbackFuture, GrokDnsResolutionError, GrokDnsResolutionPlan,
|
||||
GrokDnsResolutionPolicy, GrokEndpointPolicy, GrokHeader, GrokHeaderValue,
|
||||
GrokInferenceChunkStream, GrokInferenceClientCacheStatus, GrokInferenceDnsObservation,
|
||||
GrokInferenceDnsSource, GrokInferenceRequest, GrokInferenceResponse, GrokInferenceTransport,
|
||||
GrokInferenceTransportError, GrokInferenceTransportErrorKind, GrokInferenceTransportFuture,
|
||||
GrokInferenceTransportMetrics, GrokModelCatalogClient, GrokModelCatalogError,
|
||||
GrokModelCatalogRequest, GrokModelCatalogSession, GrokModelCatalogSessionError,
|
||||
GrokModelCatalogSnapshot, GrokModelCatalogTransport, GrokModelCatalogTransportError,
|
||||
GrokModelCatalogTransportErrorKind, GrokModelCatalogTransportFuture,
|
||||
GrokModelCatalogTransportResponse, GrokProviderConfigError, GrokProviderTransport,
|
||||
GrokRequestEncodeError, GrokReqwestTransportBuildError, GrokResponsesRequest,
|
||||
GrokSessionAffinityKey, GrokSessionBinding, GrokSessionDataError, GrokSessionLeaseGuard,
|
||||
GrokSessionSelection, GrokSessionSelector, GrokSessionSelectorError, GrokSessionSelectorFuture,
|
||||
MAX_GROK_BILLING_BYTES, MAX_GROK_MODEL_CATALOG_BYTES, OfficialGrokEndpointPolicy,
|
||||
ReqwestGrokInferenceTransport, ReqwestGrokModelCatalogTransport, ReqwestOAuthTransport,
|
||||
SelectedGrokSession, XAI_PROVIDER_NAME, build_grok_headers, grok_billing_breakdown,
|
||||
parse_grok_billing, parse_grok_model_catalog,
|
||||
};
|
||||
|
||||
/// xAI 初始化后交给组装根的最小能力集。
|
||||
|
||||
@@ -430,7 +430,9 @@ fn default_grok_model_presentation() -> ModelPresentation {
|
||||
)
|
||||
.with_reasoning(
|
||||
Some("medium".to_owned()),
|
||||
["low", "medium", "high"].map(str::to_owned).to_vec(),
|
||||
["low", "medium", "high", "xhigh"]
|
||||
.map(str::to_owned)
|
||||
.to_vec(),
|
||||
)
|
||||
.with_context_window_tokens(Some(500_000))
|
||||
.with_image_input(true)
|
||||
@@ -444,17 +446,35 @@ fn grok_model_presentation(model: &GrokCatalogModel) -> ModelPresentation {
|
||||
DEFAULT_GROK_MODEL | "grok-4.5-latest" | "grok-4.5-build-free" | "grok-build-latest"
|
||||
);
|
||||
let reasoning_evidence = model.capabilities().reasoning_effort();
|
||||
let catalog_reasoning_efforts = model
|
||||
.capabilities()
|
||||
.reasoning_efforts()
|
||||
.iter()
|
||||
.map(|effort| effort.as_str().to_owned())
|
||||
.collect::<Vec<_>>();
|
||||
let catalog_default_reasoning = model
|
||||
.capabilities()
|
||||
.default_reasoning_effort()
|
||||
.map(|effort| effort.as_str().to_owned());
|
||||
let (default_reasoning, reasoning_efforts) = match reasoning_evidence {
|
||||
GrokCatalogCapabilityEvidence::DeclaredNative if !catalog_reasoning_efforts.is_empty() => (
|
||||
catalog_default_reasoning.or_else(|| catalog_reasoning_efforts.first().cloned()),
|
||||
catalog_reasoning_efforts,
|
||||
),
|
||||
GrokCatalogCapabilityEvidence::DeclaredNative => (
|
||||
Some("medium".to_owned()),
|
||||
["low", "medium", "high"].map(str::to_owned).to_vec(),
|
||||
catalog_default_reasoning.or_else(|| Some("medium".to_owned())),
|
||||
["low", "medium", "high", "xhigh"]
|
||||
.map(str::to_owned)
|
||||
.to_vec(),
|
||||
),
|
||||
GrokCatalogCapabilityEvidence::DeclaredUnsupported => {
|
||||
(Some("none".to_owned()), vec!["none".to_owned()])
|
||||
}
|
||||
GrokCatalogCapabilityEvidence::Unknown if known_grok_4_5 => (
|
||||
Some("medium".to_owned()),
|
||||
["low", "medium", "high"].map(str::to_owned).to_vec(),
|
||||
catalog_default_reasoning.or_else(|| Some("medium".to_owned())),
|
||||
["low", "medium", "high", "xhigh"]
|
||||
.map(str::to_owned)
|
||||
.to_vec(),
|
||||
),
|
||||
GrokCatalogCapabilityEvidence::Unknown => (None, Vec::new()),
|
||||
};
|
||||
|
||||
@@ -589,11 +589,42 @@ pub enum GrokCatalogApiBackend {
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// Grok 官方目录允许声明的 reasoning effort。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum GrokCatalogReasoningEffort {
|
||||
None,
|
||||
Minimal,
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
Xhigh,
|
||||
Max,
|
||||
}
|
||||
|
||||
impl GrokCatalogReasoningEffort {
|
||||
/// 返回 Responses API 使用的规范值。
|
||||
#[must_use]
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::None => "none",
|
||||
Self::Minimal => "minimal",
|
||||
Self::Low => "low",
|
||||
Self::Medium => "medium",
|
||||
Self::High => "high",
|
||||
Self::Xhigh => "xhigh",
|
||||
Self::Max => "max",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Grok 目录中允许进入控制面的能力证据。
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct GrokCatalogCapabilities {
|
||||
responses_api: GrokCatalogCapabilityEvidence,
|
||||
reasoning_effort: GrokCatalogCapabilityEvidence,
|
||||
reasoning_efforts: Vec<GrokCatalogReasoningEffort>,
|
||||
default_reasoning_effort: Option<GrokCatalogReasoningEffort>,
|
||||
backend_search: GrokCatalogCapabilityEvidence,
|
||||
streaming_tool_calls: GrokCatalogCapabilityEvidence,
|
||||
api_backend: Option<GrokCatalogApiBackend>,
|
||||
@@ -612,6 +643,18 @@ impl GrokCatalogCapabilities {
|
||||
self.reasoning_effort
|
||||
}
|
||||
|
||||
/// 返回上游声明的可选 reasoning effort,顺序与目录一致。
|
||||
#[must_use]
|
||||
pub fn reasoning_efforts(&self) -> &[GrokCatalogReasoningEffort] {
|
||||
&self.reasoning_efforts
|
||||
}
|
||||
|
||||
/// 返回上游声明或菜单推导出的默认 reasoning effort。
|
||||
#[must_use]
|
||||
pub const fn default_reasoning_effort(&self) -> Option<GrokCatalogReasoningEffort> {
|
||||
self.default_reasoning_effort
|
||||
}
|
||||
|
||||
/// 返回后端 search 支持证据。
|
||||
#[must_use]
|
||||
pub const fn backend_search(&self) -> GrokCatalogCapabilityEvidence {
|
||||
@@ -850,6 +893,10 @@ struct GrokModelWire {
|
||||
alias = "supports_reasoning_effort"
|
||||
)]
|
||||
supports_reasoning_effort: Option<bool>,
|
||||
#[serde(rename = "reasoningEffort", alias = "reasoning_effort")]
|
||||
reasoning_effort: Option<GrokCatalogReasoningEffort>,
|
||||
#[serde(default, rename = "reasoningEfforts", alias = "reasoning_efforts")]
|
||||
reasoning_efforts: Vec<GrokReasoningEffortOptionWire>,
|
||||
#[serde(rename = "supportsBackendSearch", alias = "supports_backend_search")]
|
||||
supports_backend_search: Option<bool>,
|
||||
#[serde(rename = "streamToolCalls", alias = "stream_tool_calls")]
|
||||
@@ -857,6 +904,26 @@ struct GrokModelWire {
|
||||
hidden: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum GrokReasoningEffortOptionWire {
|
||||
Bare(GrokCatalogReasoningEffort),
|
||||
Detailed {
|
||||
value: GrokCatalogReasoningEffort,
|
||||
#[serde(default)]
|
||||
default: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl GrokReasoningEffortOptionWire {
|
||||
fn into_parts(self) -> (GrokCatalogReasoningEffort, bool) {
|
||||
match self {
|
||||
Self::Bare(value) => (value, false),
|
||||
Self::Detailed { value, default } => (value, default),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析唯一受支持的 Grok 官方完整快照。
|
||||
///
|
||||
/// # Errors
|
||||
@@ -922,15 +989,36 @@ fn normalize_model(wire: GrokModelWire) -> Result<GrokCatalogModel, GrokModelCat
|
||||
.api_backend
|
||||
.filter(|backend| *backend != GrokCatalogApiBackend::Unknown);
|
||||
let responses_api = responses_evidence(wire.supported_in_api, api_backend);
|
||||
let mut menu_default = None;
|
||||
let reasoning_efforts = wire
|
||||
.reasoning_efforts
|
||||
.into_iter()
|
||||
.map(|option| {
|
||||
let (effort, is_default) = option.into_parts();
|
||||
if is_default && menu_default.is_none() {
|
||||
menu_default = Some(effort);
|
||||
}
|
||||
effort
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let default_reasoning_effort = wire
|
||||
.reasoning_effort
|
||||
.or(menu_default)
|
||||
.or_else(|| reasoning_efforts.first().copied());
|
||||
let reasoning_effort = if reasoning_efforts.is_empty() {
|
||||
GrokCatalogCapabilityEvidence::from_wire(wire.supports_reasoning_effort)
|
||||
} else {
|
||||
GrokCatalogCapabilityEvidence::DeclaredNative
|
||||
};
|
||||
|
||||
Ok(GrokCatalogModel {
|
||||
request_model,
|
||||
display_name: wire.name,
|
||||
capabilities: GrokCatalogCapabilities {
|
||||
responses_api,
|
||||
reasoning_effort: GrokCatalogCapabilityEvidence::from_wire(
|
||||
wire.supports_reasoning_effort,
|
||||
),
|
||||
reasoning_effort,
|
||||
reasoning_efforts,
|
||||
default_reasoning_effort,
|
||||
backend_search: GrokCatalogCapabilityEvidence::from_wire(wire.supports_backend_search),
|
||||
streaming_tool_calls: GrokCatalogCapabilityEvidence::from_wire(wire.stream_tool_calls),
|
||||
api_backend,
|
||||
|
||||
@@ -21,10 +21,10 @@ pub use catalog::{
|
||||
GrokBillingRequest, GrokBillingSnapshot, GrokBillingTransport, GrokBillingTransportError,
|
||||
GrokBillingTransportErrorKind, GrokBillingTransportFuture, GrokBillingTransportResponse,
|
||||
GrokCatalogApiBackend, GrokCatalogCapabilities, GrokCatalogCapabilityEvidence,
|
||||
GrokCatalogLimits, GrokCatalogMetadata, GrokCatalogModel, GrokModelCatalogClient,
|
||||
GrokModelCatalogError, GrokModelCatalogRequest, GrokModelCatalogSession,
|
||||
GrokModelCatalogSessionError, GrokModelCatalogSnapshot, GrokModelCatalogTransport,
|
||||
GrokModelCatalogTransportError, GrokModelCatalogTransportErrorKind,
|
||||
GrokCatalogLimits, GrokCatalogMetadata, GrokCatalogModel, GrokCatalogReasoningEffort,
|
||||
GrokModelCatalogClient, GrokModelCatalogError, GrokModelCatalogRequest,
|
||||
GrokModelCatalogSession, GrokModelCatalogSessionError, GrokModelCatalogSnapshot,
|
||||
GrokModelCatalogTransport, GrokModelCatalogTransportError, GrokModelCatalogTransportErrorKind,
|
||||
GrokModelCatalogTransportFuture, GrokModelCatalogTransportResponse, MAX_GROK_BILLING_BYTES,
|
||||
MAX_GROK_MODEL_CATALOG_BYTES, parse_grok_billing, parse_grok_model_catalog,
|
||||
};
|
||||
|
||||
@@ -1827,7 +1827,7 @@ async fn provider_compiles_realtime_catalog_capabilities() {
|
||||
assert_eq!(presentation.display_name(), Some("Grok 4.5"));
|
||||
assert_eq!(
|
||||
presentation.supported_reasoning_efforts(),
|
||||
["low", "medium", "high"]
|
||||
["low", "medium", "high", "xhigh"]
|
||||
);
|
||||
assert_eq!(presentation.context_window_tokens(), Some(1_000_000));
|
||||
assert!(presentation.agent_tools());
|
||||
|
||||
@@ -261,6 +261,16 @@ fn official_fixture_should_use_actual_model_and_whitelisted_metadata() {
|
||||
(
|
||||
model.capabilities().responses_api(),
|
||||
model.capabilities().reasoning_effort(),
|
||||
model
|
||||
.capabilities()
|
||||
.reasoning_efforts()
|
||||
.iter()
|
||||
.map(|effort| effort.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
model
|
||||
.capabilities()
|
||||
.default_reasoning_effort()
|
||||
.map(|effort| effort.as_str()),
|
||||
model.capabilities().backend_search(),
|
||||
model.capabilities().streaming_tool_calls(),
|
||||
),
|
||||
@@ -278,6 +288,8 @@ fn official_fixture_should_use_actual_model_and_whitelisted_metadata() {
|
||||
(
|
||||
GrokCatalogCapabilityEvidence::DeclaredNative,
|
||||
GrokCatalogCapabilityEvidence::DeclaredNative,
|
||||
vec!["low", "medium", "high", "xhigh"],
|
||||
Some("medium"),
|
||||
GrokCatalogCapabilityEvidence::DeclaredNative,
|
||||
GrokCatalogCapabilityEvidence::DeclaredNative,
|
||||
),
|
||||
@@ -335,6 +347,35 @@ fn missing_capability_fields_should_remain_unknown() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reasoning_effort_menu_should_be_capability_evidence_without_legacy_flag() {
|
||||
let snapshot = parse_grok_model_catalog(
|
||||
br#"{"object":"list","data":[{"id":"grok-reasoning","reasoningEfforts":[{"value":"xhigh","default":true},"low"]}]}"#,
|
||||
None,
|
||||
)
|
||||
.expect("reasoning effort menu");
|
||||
let capabilities = snapshot.models()[0].capabilities();
|
||||
|
||||
assert_eq!(
|
||||
(
|
||||
capabilities.reasoning_effort(),
|
||||
capabilities
|
||||
.reasoning_efforts()
|
||||
.iter()
|
||||
.map(|effort| effort.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
capabilities
|
||||
.default_reasoning_effort()
|
||||
.map(|effort| effort.as_str()),
|
||||
),
|
||||
(
|
||||
GrokCatalogCapabilityEvidence::DeclaredNative,
|
||||
vec!["xhigh", "low"],
|
||||
Some("xhigh"),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_id_should_take_priority_over_catalog_id() {
|
||||
let snapshot = parse_grok_model_catalog(
|
||||
|
||||
+7
@@ -12,6 +12,13 @@
|
||||
"apiBackend": "responses",
|
||||
"supportedInApi": true,
|
||||
"supportsReasoningEffort": true,
|
||||
"reasoningEffort": "medium",
|
||||
"reasoningEfforts": [
|
||||
"low",
|
||||
{ "value": "medium", "label": "Balanced", "default": true },
|
||||
"high",
|
||||
{ "id": "extended", "value": "xhigh", "label": "Extended" }
|
||||
],
|
||||
"supportsBackendSearch": true,
|
||||
"streamToolCalls": true,
|
||||
"hidden": false,
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
|
||||
/>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg?v=20260726" />
|
||||
<title>Codex Proxy RS</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<rect width="64" height="64" rx="16" fill="#111827"/>
|
||||
<g transform="translate(10 8) scale(1.85)" fill="none" stroke="#fff" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 5c.67 0 1.35.09 2 .26 1.78-2 5.03-2.84 6.42-2.26 1.4.58-.42 7-.42 7 .57 1.07 1 2.24 1 3.44C21 17.9 16.97 21 12 21s-9-3.1-9-7.56c0-1.25.5-2.4 1-3.44 0 0-1.89-6.42-.5-7S8.22 3.26 10 5.26A8.3 8.3 0 0 1 12 5Z"/>
|
||||
<path d="M8 14v.5"/>
|
||||
<path d="M16 14v.5"/>
|
||||
<path d="M11.25 16.25h1.5L12 17l-.75-.75Z"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="48 48 416 416" role="img" aria-labelledby="title">
|
||||
<title id="title">Codex Proxy</title>
|
||||
<rect x="48" y="48" width="416" height="416" rx="88" fill="#1c2a3d" />
|
||||
<g fill="none" stroke="#fff" stroke-linecap="round" stroke-width="36">
|
||||
<path d="M156 307A112 112 0 0 1 307 156" />
|
||||
<path d="M356 205A112 112 0 0 1 205 356" />
|
||||
</g>
|
||||
<path d="m407 105-119 153-183 149 124-157Z" fill="#fff" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 594 B After Width: | Height: | Size: 457 B |
@@ -108,7 +108,11 @@ const labelClasses = computed(() => [
|
||||
:title="title || (iconOnly ? label : undefined)"
|
||||
:aria-busy="loading"
|
||||
>
|
||||
<LoaderCircle v-if="loading" class="animate-spin" :size="loadingIconSize[size]" />
|
||||
<LoaderCircle
|
||||
v-if="loading"
|
||||
class="shrink-0 animate-spin origin-center transform-view will-change-transform"
|
||||
:size="loadingIconSize[size]"
|
||||
/>
|
||||
<span v-if="$slots.icon && !loading" class="inline-flex shrink-0">
|
||||
<slot name="icon" />
|
||||
</span>
|
||||
|
||||
@@ -24,6 +24,11 @@ const props = withDefaults(
|
||||
|
||||
const model = defineModel<string>({ default: '' })
|
||||
|
||||
// number 输入也保持字符串契约,避免原生 v-model 隐式转换为 number。
|
||||
function updateModel(event: Event) {
|
||||
model.value = (event.target as HTMLInputElement).value
|
||||
}
|
||||
|
||||
const containerClasses = computed(() => [
|
||||
'relative inline-flex h-(--cp-input-height-default) w-full min-w-0 items-center gap-2.5 overflow-visible rounded-(--cp-input-radius-base) border-0 px-3.5 text-[13px] text-(--cp-text-primary) shadow-(--cp-shadow-input) transition-[background-color,box-shadow,color] duration-[160ms]',
|
||||
props.disabled
|
||||
@@ -64,7 +69,7 @@ const inputClasses = computed(() => [
|
||||
<slot name="prefix" />
|
||||
</span>
|
||||
<input
|
||||
v-model="model"
|
||||
:value="model"
|
||||
class="base-input__field"
|
||||
:class="inputClasses"
|
||||
:name="name"
|
||||
@@ -74,6 +79,7 @@ const inputClasses = computed(() => [
|
||||
:autocomplete="autocomplete"
|
||||
:aria-label="ariaLabel"
|
||||
:aria-invalid="error ? 'true' : undefined"
|
||||
@input="updateModel"
|
||||
>
|
||||
<span v-if="$slots.suffix" :class="iconClasses">
|
||||
<slot name="suffix" />
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ArrowUpCircle,
|
||||
Cat,
|
||||
ChartNoAxesColumn,
|
||||
Info,
|
||||
KeyRound,
|
||||
@@ -340,9 +339,14 @@ onBeforeUnmount(() => {
|
||||
<BaseMotionIcon
|
||||
aria-hidden="true"
|
||||
variant="brand"
|
||||
class="inline-flex size-11 items-center justify-center relative -top-0.5 rounded-(--cp-icon-button-radius) bg-(--cp-bg-muted) text-(--cp-text-primary)"
|
||||
class="inline-flex size-11 items-center justify-center relative -top-0.5 rounded-(--cp-icon-button-radius) bg-(--cp-brand-mark-bg)"
|
||||
>
|
||||
<Cat :size="27" stroke-width="2" />
|
||||
<img
|
||||
src="/favicon.svg"
|
||||
alt=""
|
||||
class="block size-9 select-none"
|
||||
draggable="false"
|
||||
>
|
||||
</BaseMotionIcon>
|
||||
<span
|
||||
v-show="brandLabelVisible"
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
--cp-bg-tertiary: #eef6ff;
|
||||
--cp-bg-nav-active: #e9eef5;
|
||||
--cp-bg-dark: #111827;
|
||||
--cp-brand-mark-bg: #1c2a3d;
|
||||
--cp-white: #ffffff;
|
||||
--cp-transparent: #ffffff00;
|
||||
--cp-overlay-scrim: #0e17264d;
|
||||
|
||||
@@ -35,19 +35,30 @@ const configPath = computed(() =>
|
||||
? '%userprofile%\\.codex\\config.toml'
|
||||
: '~/.codex/config.toml',
|
||||
)
|
||||
const authPath = computed(() =>
|
||||
activePlatform.value === 'windows' ? '%userprofile%\\.codex\\auth.json' : '~/.codex/auth.json',
|
||||
const tokenPath = computed(() =>
|
||||
activePlatform.value === 'windows'
|
||||
? '%userprofile%\\.codex\\codex-proxy.token'
|
||||
: '~/.codex/codex-proxy.token',
|
||||
)
|
||||
const codexAuthJson = computed(() => JSON.stringify({ OPENAI_API_KEY: keyValue.value }, null, 2))
|
||||
const defaultModel = computed(() =>
|
||||
props.apiKey?.providerKind?.trim().toLowerCase() === 'xai' ? 'grok-4.5' : 'gpt-5.5',
|
||||
)
|
||||
const providerAuthToml = computed(() =>
|
||||
activePlatform.value === 'windows'
|
||||
? `[model_providers.OpenAI.auth]
|
||||
command = "powershell.exe"
|
||||
args = ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", "Get-Content -Raw (Join-Path (Join-Path $env:USERPROFILE '.codex') 'codex-proxy.token')"]`
|
||||
: `[model_providers.OpenAI.auth]
|
||||
command = "sh"
|
||||
args = ["-c", 'cat "$HOME/.codex/codex-proxy.token"']`,
|
||||
)
|
||||
|
||||
const codexConfigToml = computed(
|
||||
() => `model_provider = "OpenAI"
|
||||
model = "${defaultModel.value}"
|
||||
review_model = "${defaultModel.value}"
|
||||
model_reasoning_effort = "xhigh"
|
||||
service_tier = "default"
|
||||
disable_response_storage = true
|
||||
network_access = "enabled"
|
||||
windows_wsl_setup_acknowledged = true
|
||||
@@ -56,7 +67,8 @@ windows_wsl_setup_acknowledged = true
|
||||
name = "OpenAI"
|
||||
base_url = "${props.apiBaseUrl}"
|
||||
wire_api = "responses"
|
||||
requires_openai_auth = true
|
||||
|
||||
${providerAuthToml.value}
|
||||
|
||||
[features]
|
||||
goals = true`,
|
||||
@@ -64,7 +76,7 @@ goals = true`,
|
||||
|
||||
const visibleFiles = computed(() => [
|
||||
{ path: configPath.value, content: codexConfigToml.value },
|
||||
{ path: authPath.value, content: codexAuthJson.value },
|
||||
{ path: tokenPath.value, content: keyValue.value },
|
||||
])
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ArrowRight, Cat, CircleAlert, Eye, EyeOff, KeyRound, Mail, Moon, Sun } from '@lucide/vue'
|
||||
import { ArrowRight, CircleAlert, Eye, EyeOff, KeyRound, Mail, Moon, Sun } from '@lucide/vue'
|
||||
import { computed, shallowRef } from 'vue'
|
||||
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
@@ -58,7 +58,12 @@ function togglePasswordVisible(): void {
|
||||
<header class="flex min-w-0 items-center justify-between gap-4.5 max-[560px]:gap-3.5">
|
||||
<div class="flex min-w-0 items-center gap-3">
|
||||
<BaseMotionIcon aria-hidden="true" variant="brand" class="login-logo">
|
||||
<Cat :size="20" :stroke-width="2.1" />
|
||||
<img
|
||||
src="/favicon.svg"
|
||||
alt=""
|
||||
class="block size-8 select-none"
|
||||
draggable="false"
|
||||
>
|
||||
</BaseMotionIcon>
|
||||
<span class="grid min-w-0 gap-1">
|
||||
<strong
|
||||
|
||||
@@ -93,7 +93,7 @@ async function handleSubmit(): Promise<void> {
|
||||
--login-form-bg-c: color-mix(in srgb, var(--cp-bg-muted) 78%, var(--cp-info-bg));
|
||||
--login-form-shadow: color-mix(in srgb, var(--cp-text-primary) 20%, transparent);
|
||||
--login-form-line: color-mix(in srgb, var(--cp-info-border) 54%, transparent);
|
||||
--login-logo-bg: color-mix(in srgb, var(--cp-info-bg) 74%, var(--cp-bg-surface));
|
||||
--login-logo-bg: var(--cp-brand-mark-bg);
|
||||
--login-logo-text: var(--cp-accent-primary);
|
||||
--login-title: var(--cp-text-primary);
|
||||
--login-brand-title: var(--cp-text-strong);
|
||||
@@ -159,7 +159,7 @@ async function handleSubmit(): Promise<void> {
|
||||
--login-form-bg-c: color-mix(in srgb, var(--cp-bg-page) 90%, transparent);
|
||||
--login-form-shadow: color-mix(in srgb, var(--cp-bg-dark) 70%, transparent);
|
||||
--login-form-line: color-mix(in srgb, var(--cp-info-text) 66%, transparent);
|
||||
--login-logo-bg: color-mix(in srgb, var(--cp-bg-surface) 84%, transparent);
|
||||
--login-logo-bg: var(--cp-brand-mark-bg);
|
||||
--login-logo-text: var(--cp-info-text);
|
||||
--login-title: var(--cp-text-strong);
|
||||
--login-brand-title: var(--cp-white);
|
||||
|
||||
@@ -1 +1 @@
|
||||
version: "3.0.5"
|
||||
version: "3.0.8"
|
||||
|
||||
Reference in New Issue
Block a user