Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/tw-api/msg-codes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ gw.config.proxy_unusable
gw.config.security_rules passthrough
gw.content.refused
gw.convert.failed
gw.files.unsupported
gw.hidden_text.refused_message
gw.hidden_text.refused_tool_result
gw.internal
Expand Down
19 changes: 19 additions & 0 deletions crates/tw-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,10 @@ pub const MSG_CODES: &str = include_str!("../msg-codes.txt");
/// **23 起额度也来自 GLM Coding Plan**(Z.ai / BigModel 的上游):窗口多了 `monthly`,
/// 积分制套餐的窗口带上 [`QuotaWindow::credits`](总额、已用、剩余)。照 22 写的界面
/// 不认 `monthly`,也看不到剩余积分。
///
/// 同一版起**记录说得出请求带没带 DeepSeek Harness 的会话日志**:`RequestStarted` 和
/// [`HistoryRow`] 多了 `session_log_bytes`(`dsh_session_log` 的字节数,没带的没有)。
/// 照 22 写的界面看不到它。
pub const CONTROL_API_VERSION: u32 = 23;

#[derive(Debug, Clone, Serialize, Deserialize)]
Expand Down Expand Up @@ -723,6 +727,15 @@ pub enum Event {
model: String,
method: String,
path: String,
/// 请求带着 DeepSeek Harness 的会话日志(`dsh_session_log`):这是它序列化之后
/// 的字节数。没带是 None。
///
/// **会话日志是整段对话**(工作目录、系统提示、每一轮的输入输出、工具的参数和
/// 结果),默认开着,每个请求补上一段,单次最多 8 MiB。它不进模型输入,只有
/// DeepSeek 官方收:发给别的上游之前网关会去掉它,界面要能说出「这个请求带着
/// 会话日志、有多大」
#[serde(default, skip_serializing_if = "Option::is_none")]
session_log_bytes: Option<u64>,
at_ms: u64,
},
/// 收到上游响应头。**这个事件单独存在是有意的**:流式请求从这里
Expand Down Expand Up @@ -3105,6 +3118,10 @@ pub struct HistoryRow {
/// 请求带的那把网关密钥打码后的样子(`tw-re…wb4e`),请求那一刻的
#[serde(default, skip_serializing_if = "Option::is_none")]
pub key_masked: Option<String>,
/// 请求带着 DeepSeek Harness 的会话日志:它的字节数。没带的没有,理由见
/// `Event::RequestStarted::session_log_bytes`
#[serde(default, skip_serializing_if = "Option::is_none")]
pub session_log_bytes: Option<i64>,
/// 这次请求在两项防护上留下的记录(和安全日志同一份)。没有就是空的。
///
/// **流量页的徽标靠它。**以前徽标只来自实时事件,关窗再开就没了 ——
Expand Down Expand Up @@ -4270,6 +4287,7 @@ mod tests {
model: "m".into(),
method: "POST".into(),
path: "/v1/messages".into(),
session_log_bytes: None,
at_ms: 0,
},
Event::RequestHeaders {
Expand Down Expand Up @@ -4327,6 +4345,7 @@ mod tests {
model: "m".into(),
method: "POST".into(),
path: "/v1/messages".into(),
session_log_bytes: None,
at_ms: 0,
};
let v = serde_json::to_value(&e).unwrap();
Expand Down
1 change: 1 addition & 0 deletions crates/tw-control/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1106,6 +1106,7 @@ fn history_row(
client_hint: r.client_hint,
peer: r.peer,
key_masked: r.key_masked,
session_log_bytes: r.session_log_bytes,
security,
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/tw-control/tests/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ async fn replaying_a_truncated_body_is_refused_rather_than_misleading() {
let db = tw_store::Db::open(&d.path().join("data.db")).unwrap();
let blobs = tw_store::Blobs::new(d.path().join("blobs"));
let mut row = tw_store::db::RequestRow {
session_log_bytes: None,
key_masked: None,
peer: None,
id: 1,
Expand Down
1 change: 1 addition & 0 deletions crates/tw-control/tests/in_flight.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ fn started(id: u64, model: &str) -> tw_api::Event {
model: model.into(),
method: "POST".into(),
path: "/v1/messages".into(),
session_log_bytes: None,
at_ms: 1_000 + id,
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/tw-control/tests/live_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ fn started(id: u64) -> tw_api::Event {
model: "claude-sonnet-4-5".into(),
method: "POST".into(),
path: "/v1/messages".into(),
session_log_bytes: None,
at_ms: 1_758_000_000_000,
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/tw-control/tests/replay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ fn system_proxy() {

fn row(id: i64, provider: &str) -> tw_store::db::RequestRow {
tw_store::db::RequestRow {
session_log_bytes: None,
key_masked: None,
peer: None,
id,
Expand Down
1 change: 1 addition & 0 deletions crates/tw-control/tests/security.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ impl Bed {

fn request(id: i64, at_ms: i64, provider: &str) -> tw_store::db::RequestRow {
tw_store::db::RequestRow {
session_log_bytes: None,
key_masked: None,
peer: None,
id,
Expand Down
1 change: 1 addition & 0 deletions crates/tw-control/tests/sessions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const BASE: &str = "version: 1\nlisten:\n control:\n key: c0ffee00c0ffee00c0
fn turn(id: i64, billing: &str) -> tw_store::db::RequestRow {
let per_token = billing == "per-token";
tw_store::db::RequestRow {
session_log_bytes: None,
key_masked: None,
peer: None,
id,
Expand Down
1 change: 1 addition & 0 deletions crates/tw-control/tests/summary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const BASE: &str = "version: 1\nlisten:\n control:\n key: c0ffee00c0ffee00c0
/// 一条有用量、有价格的请求。
fn req(id: i64, model: &str) -> tw_store::db::RequestRow {
tw_store::db::RequestRow {
session_log_bytes: None,
key_masked: None,
peer: None,
id,
Expand Down
20 changes: 18 additions & 2 deletions crates/tw-dialect/src/anthropic/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,23 @@ pub fn decode_request(v: &Value, dropped: &mut Dropped) -> Result<Request, Rejec
for m in arr_of(v, "messages") {
let role = match str_of(m, "role") {
Some("assistant") => Role::Assistant,
// 消息里的 system 角色:并进系统提示,位置信息丢失但内容保留
// 消息里的 system 角色:并进系统提示,位置信息丢失但内容保留。DeepSeek
// Harness 在这里放改过的系统提示,还有对话中途增删工具的 `tool_addition` /
// `tool_removal`:别家没有这种写法
Some("system") => {
let t = text_of(m.get("content").unwrap_or(&Value::Null));
let content = m.get("content").unwrap_or(&Value::Null);
let t = text_of(content);
if !t.is_empty() {
r.system.push(t);
}
for b in content.as_array().into_iter().flatten() {
match str_of(b, "type") {
Some("text") => {}
other => {
dropped.path(format!("messages.content.{}", other.unwrap_or("unknown")))
}
}
}
continue;
}
_ => Role::User,
Expand All @@ -59,6 +70,11 @@ pub fn decode_request(v: &Value, dropped: &mut Dropped) -> Result<Request, Rejec
}

for t in arr_of(v, "tools") {
// 推迟加载的工具:等工具搜索或者 `tool_addition` 来加载它,别家没有这回事,
// 发过去的是一个一开始就能用的工具
if t.get("defer_loading").and_then(Value::as_bool) == Some(true) {
dropped.path("tools.defer_loading");
}
match str_of(t, "type") {
None | Some("custom") => r.tools.push(Tool {
name: str_of(t, "name").unwrap_or_default().to_string(),
Expand Down
21 changes: 21 additions & 0 deletions crates/tw-dialect/src/chat/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,27 @@ pub fn decode_request(
summary: e.is_some(),
});
}
// DeepSeek、GLM、Kimi 的写法:`thinking.type` 开关推理,强度另写在 `reasoning_effort`。
// 关掉时以它为准
match v.get("thinking").and_then(|t| str_of(t, "type")) {
Some("disabled") => {
r.reasoning = Some(Reasoning {
enabled: false,
effort: None,
budget: None,
summary: false,
})
}
Some("enabled") if r.reasoning.is_none() => {
r.reasoning = Some(Reasoning {
enabled: true,
effort: None,
budget: None,
summary: true,
})
}
_ => {}
}

r.format = match v.get("response_format").and_then(|f| str_of(f, "type")) {
Some("json_object") => Some(Format::JsonObject),
Expand Down
2 changes: 1 addition & 1 deletion crates/tw-dialect/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
//! ```
//!
//! **同格式不经过这里**:客户端和上游说同一种格式时,网关原样转发,一个字节都不改。
//! 唯一的例外见 [`strip_carried`]。
//! 例外见 [`strip_carried`] 和 [`crate::harness::clean`]。

use std::collections::{HashMap, HashSet};

Expand Down
Loading
Loading