22//!
33//! - GET /api/admin/models:全部模型列表(含 id / 所有字段,供管理表格)
44//! - POST /api/admin/models:新增 {provider, model, currency, input_per_m, output_per_m,
5- //! context_length?, max_output?, vision?, cache_hit_input_per_m?, peak_input_per_m ?,
6- //! peak_output_per_m?, peak_cache_hit_input_per_m?}
5+ //! context_length?, max_output?, vision?, cache_hit_input_per_m?, cache_write_input_per_m ?,
6+ //! peak_input_per_m?, peak_output_per_m?, peak_cache_hit_input_per_m?}
77//! 校验:provider/model 非空、价格 ≥ 0、currency ∈ {USD, CNY}、(provider, model) 唯一冲突 409
88//! - PATCH /api/admin/models/:id:部分更新(任意字段)
99//! - DELETE /api/admin/models/:id:直接删除(只删目录行、**不改写 keys**;删除的后果由路由入口的
@@ -40,6 +40,9 @@ pub struct ModelCreate {
4040 pub vision : i64 ,
4141 #[ serde( default ) ]
4242 pub cache_hit_input_per_m : f64 ,
43+ /// 缓存写入(cache-creation)输入价(#433;缺省 0 = 缓存写入不收费)
44+ #[ serde( default ) ]
45+ pub cache_write_input_per_m : f64 ,
4346 /// 高峰时段价(rant 2026-08-20T11:58:40;缺省 0 = 不启用高峰计费)
4447 #[ serde( default ) ]
4548 pub peak_input_per_m : f64 ,
@@ -65,19 +68,21 @@ pub struct ModelPatch {
6568 pub max_output : Option < i64 > ,
6669 pub vision : Option < i64 > ,
6770 pub cache_hit_input_per_m : Option < f64 > ,
71+ pub cache_write_input_per_m : Option < f64 > ,
6872 pub peak_input_per_m : Option < f64 > ,
6973 pub peak_output_per_m : Option < f64 > ,
7074 pub peak_cache_hit_input_per_m : Option < f64 > ,
7175}
7276
7377/// 校验通用字段:价格 ≥ 0、vision ∈ {0,1}、currency 枚举
74- #[ allow( clippy:: too_many_arguments) ] // 价格字段平铺(6 个价格 + currency + vision),保持与请求结构体同序
78+ #[ allow( clippy:: too_many_arguments) ] // 价格字段平铺(7 个价格 + currency + vision),保持与请求结构体同序
7579fn validate_common (
7680 currency : & str ,
7781 input_per_m : f64 ,
7882 output_per_m : f64 ,
7983 vision : i64 ,
8084 cache_hit_input_per_m : f64 ,
85+ cache_write_input_per_m : f64 ,
8186 peak_input_per_m : f64 ,
8287 peak_output_per_m : f64 ,
8388 peak_cache_hit_input_per_m : f64 ,
@@ -91,6 +96,7 @@ fn validate_common(
9196 if input_per_m < 0.0
9297 || output_per_m < 0.0
9398 || cache_hit_input_per_m < 0.0
99+ || cache_write_input_per_m < 0.0
94100 || peak_input_per_m < 0.0
95101 || peak_output_per_m < 0.0
96102 || peak_cache_hit_input_per_m < 0.0
@@ -122,18 +128,19 @@ fn row_to_json(r: &rusqlite::Row) -> rusqlite::Result<serde_json::Value> {
122128 "max_output" : r. get:: <_, i64 >( 7 ) ?,
123129 "vision" : r. get:: <_, i64 >( 8 ) ?,
124130 "cache_hit_input_per_m" : r. get:: <_, f64 >( 9 ) ?,
125- "peak_input_per_m" : r. get:: <_, f64 >( 10 ) ?,
126- "peak_output_per_m" : r. get:: <_, f64 >( 11 ) ?,
127- "peak_cache_hit_input_per_m" : r. get:: <_, f64 >( 12 ) ?,
128- "updated_at" : crate :: dao:: utc_iso( & r. get:: <_, String >( 13 ) ?) ,
129- "context_window" : r. get:: <_, i64 >( 14 ) ?,
131+ "cache_write_input_per_m" : r. get:: <_, f64 >( 10 ) ?,
132+ "peak_input_per_m" : r. get:: <_, f64 >( 11 ) ?,
133+ "peak_output_per_m" : r. get:: <_, f64 >( 12 ) ?,
134+ "peak_cache_hit_input_per_m" : r. get:: <_, f64 >( 13 ) ?,
135+ "updated_at" : crate :: dao:: utc_iso( & r. get:: <_, String >( 14 ) ?) ,
136+ "context_window" : r. get:: <_, i64 >( 15 ) ?,
130137 } ) )
131138}
132139
133140const ROW_SELECT : & str = "SELECT id, provider, model, currency, input_per_m, output_per_m, \
134141 context_length, max_output, vision, cache_hit_input_per_m, \
135- peak_input_per_m, peak_output_per_m, peak_cache_hit_input_per_m , \
136- updated_at, context_window \
142+ cache_write_input_per_m, peak_input_per_m, peak_output_per_m , \
143+ peak_cache_hit_input_per_m, updated_at, context_window \
137144 FROM models ";
138145
139146/// GET /api/admin/models:全部模型(管理表格数据源)
@@ -168,6 +175,7 @@ pub async fn create(
168175 req. output_per_m ,
169176 req. vision ,
170177 req. cache_hit_input_per_m ,
178+ req. cache_write_input_per_m ,
171179 req. peak_input_per_m ,
172180 req. peak_output_per_m ,
173181 req. peak_cache_hit_input_per_m ,
@@ -190,9 +198,9 @@ pub async fn create(
190198 . query_row (
191199 "INSERT INTO models (provider, model, currency, input_per_m, output_per_m, \
192200 context_length, max_output, vision, cache_hit_input_per_m, \
193- peak_input_per_m, peak_output_per_m, peak_cache_hit_input_per_m , \
194- context_window, updated_at) \
195- VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, datetime('now')) \
201+ cache_write_input_per_m, peak_input_per_m, peak_output_per_m , \
202+ peak_cache_hit_input_per_m, context_window, updated_at) \
203+ VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, datetime('now')) \
196204 RETURNING id",
197205 params ! [
198206 provider,
@@ -204,6 +212,7 @@ pub async fn create(
204212 req. max_output,
205213 req. vision,
206214 req. cache_hit_input_per_m,
215+ req. cache_write_input_per_m,
207216 req. peak_input_per_m,
208217 req. peak_output_per_m,
209218 req. peak_cache_hit_input_per_m,
@@ -242,6 +251,7 @@ pub async fn patch(
242251 ) ) ;
243252 }
244253 // 部分更新:先取当前值,合并后校验 + 写回(简单且避免逐列拼接 SQL)
254+ #[ allow( clippy:: type_complexity) ] // 价格平铺(13 元组),同 validate_common
245255 let cur: (
246256 String ,
247257 String ,
@@ -255,11 +265,13 @@ pub async fn patch(
255265 f64 ,
256266 f64 ,
257267 f64 ,
268+ f64 ,
258269 ) = conn
259270 . query_row (
260271 "SELECT provider, model, currency, input_per_m, output_per_m, \
261272 context_length, max_output, vision, cache_hit_input_per_m, \
262- peak_input_per_m, peak_output_per_m, peak_cache_hit_input_per_m \
273+ cache_write_input_per_m, peak_input_per_m, peak_output_per_m, \
274+ peak_cache_hit_input_per_m \
263275 FROM models WHERE id = ?1",
264276 [ id] ,
265277 |r| {
@@ -276,6 +288,7 @@ pub async fn patch(
276288 r. get ( 9 ) ?,
277289 r. get ( 10 ) ?,
278290 r. get ( 11 ) ?,
291+ r. get ( 12 ) ?,
279292 ) )
280293 } ,
281294 )
@@ -289,9 +302,10 @@ pub async fn patch(
289302 let max_output = req. max_output . unwrap_or ( cur. 6 ) ;
290303 let vision = req. vision . unwrap_or ( cur. 7 ) ;
291304 let cache_hit_input_per_m = req. cache_hit_input_per_m . unwrap_or ( cur. 8 ) ;
292- let peak_input_per_m = req. peak_input_per_m . unwrap_or ( cur. 9 ) ;
293- let peak_output_per_m = req. peak_output_per_m . unwrap_or ( cur. 10 ) ;
294- let peak_cache_hit_input_per_m = req. peak_cache_hit_input_per_m . unwrap_or ( cur. 11 ) ;
305+ let cache_write_input_per_m = req. cache_write_input_per_m . unwrap_or ( cur. 9 ) ;
306+ let peak_input_per_m = req. peak_input_per_m . unwrap_or ( cur. 10 ) ;
307+ let peak_output_per_m = req. peak_output_per_m . unwrap_or ( cur. 11 ) ;
308+ let peak_cache_hit_input_per_m = req. peak_cache_hit_input_per_m . unwrap_or ( cur. 12 ) ;
295309 if provider. is_empty ( ) || model. is_empty ( ) {
296310 return Err ( (
297311 axum:: http:: StatusCode :: BAD_REQUEST ,
@@ -304,6 +318,7 @@ pub async fn patch(
304318 output_per_m,
305319 vision,
306320 cache_hit_input_per_m,
321+ cache_write_input_per_m,
307322 peak_input_per_m,
308323 peak_output_per_m,
309324 peak_cache_hit_input_per_m,
@@ -328,10 +343,11 @@ pub async fn patch(
328343 conn. execute (
329344 "UPDATE models SET provider = ?1, model = ?2, currency = ?3, input_per_m = ?4, \
330345 output_per_m = ?5, context_length = ?6, max_output = ?7, vision = ?8, \
331- cache_hit_input_per_m = ?9, peak_input_per_m = ?10, peak_output_per_m = ?11, \
332- peak_cache_hit_input_per_m = ?12, context_window = ?6, tiers_json = '[]', \
346+ cache_hit_input_per_m = ?9, cache_write_input_per_m = ?10, \
347+ peak_input_per_m = ?11, peak_output_per_m = ?12, \
348+ peak_cache_hit_input_per_m = ?13, context_window = ?6, tiers_json = '[]', \
333349 updated_at = datetime('now') \
334- WHERE id = ?13 ",
350+ WHERE id = ?14 ",
335351 params ! [
336352 provider,
337353 model,
@@ -342,6 +358,7 @@ pub async fn patch(
342358 max_output,
343359 vision,
344360 cache_hit_input_per_m,
361+ cache_write_input_per_m,
345362 peak_input_per_m,
346363 peak_output_per_m,
347364 peak_cache_hit_input_per_m,
0 commit comments