diff --git a/Cargo.lock b/Cargo.lock index f2dc8534..a88a6c3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -775,27 +775,27 @@ dependencies = [ [[package]] name = "cranelift-assembler-x64" -version = "0.136.4" +version = "0.136.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9c587da2696fbaa58e7a2fa2e5bfdd365d0928219f36992304ac1f15e5bc254" +checksum = "249a61efca6a54425e027f5704a6f68a0232b4df527e05a3ef947d89dbdcb078" dependencies = [ "cranelift-assembler-x64-meta", ] [[package]] name = "cranelift-assembler-x64-meta" -version = "0.136.4" +version = "0.136.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28a60027b7556b680b051651291053ec381edf4ffbc01784997ae51a64269bb" +checksum = "d5336a46e05db2d515440b1ce8a9363e7bc04b608c0f66cc98dddc5492a66fbf" dependencies = [ "cranelift-srcgen", ] [[package]] name = "cranelift-bforest" -version = "0.136.4" +version = "0.136.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bfdb846acebfd591d601994ea283c98d525ac0bc9a84ca3f2c36bcf5b69ff1" +checksum = "01b3675b13214270fddbd257d5fbf4109be8b02d92fc636fe96e2829514a7235" dependencies = [ "cranelift-entity", "wasmtime-internal-core", @@ -803,9 +803,9 @@ dependencies = [ [[package]] name = "cranelift-bitset" -version = "0.136.4" +version = "0.136.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9f8cc1080739d5f72b4ee7e00aff7d589f1284beaf0a19472c07878be7f299" +checksum = "d7c1d67aa02b6e174bd2b165c113e94af9498fcc20e2892d0f4d5ab8c9618d09" dependencies = [ "serde", "serde_derive", @@ -814,9 +814,9 @@ dependencies = [ [[package]] name = "cranelift-codegen" -version = "0.136.4" +version = "0.136.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d85417d493ecec833b5134546b4a252c78cc0bd5be11729a35d25dcff9e4e70b" +checksum = "26fe6f3ec5344c32602c414e986dcbe699fa501a3e3e7b02f887d9d12b14ae42" dependencies = [ "bumpalo", "cranelift-assembler-x64", @@ -845,9 +845,9 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.136.4" +version = "0.136.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d01c8ed0098360bae69ad6b82952d4247516799ce8050d45ad168d6689f4eae6" +checksum = "96c5e6323d458486d95781defdb5016a1176fc911d8bc8e64b696adbda6e9044" dependencies = [ "cranelift-assembler-x64-meta", "cranelift-codegen-shared", @@ -858,24 +858,24 @@ dependencies = [ [[package]] name = "cranelift-codegen-shared" -version = "0.136.4" +version = "0.136.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f9b86d809cec9c4e88b201f1b3ed3b0347674c9ccfbb8c355228669e34be81" +checksum = "f1260aa93b0bd48e8e55c1c7fd9ff6a260f1a1bc46e1f0151a5055b124e5b15d" [[package]] name = "cranelift-control" -version = "0.136.4" +version = "0.136.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce182038fb10b719841e65fdf5deeee258c684f63439f5be680c2be059274b58" +checksum = "50c179632a09850f81f1a1e9116cb09330c734a86689f3ee4c3f4ec81543acbd" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.136.4" +version = "0.136.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bca812b03a7b942796391f25bc02aa70a1a007fee3ce2756490c788429e9c39" +checksum = "d8c071188cb1c3b1803e23ae415a8d5f58d741d3a4b26a79b83dd61921112eac" dependencies = [ "cranelift-bitset", "serde", @@ -885,9 +885,9 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.136.4" +version = "0.136.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c30563d2aef95e8d1a00a920199c99117b350339599f0e9487a7a1cf5d59de" +checksum = "e2b85ba0a55d7685117b275fe6f36c819d438862e4223d28f0b97902460ff0ac" dependencies = [ "cranelift-codegen", "hashbrown 0.17.1", @@ -898,15 +898,15 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.136.4" +version = "0.136.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c54184333a275698edb1d3bdff226131a25d47133d792aa716e47e32b8b7f05" +checksum = "20ed364edcaed769da7b2709d540f8f7d0ff257749ba193f475e22dba6a0178b" [[package]] name = "cranelift-native" -version = "0.136.4" +version = "0.136.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb67a4c8a3180b5f1d77dffcd6317c05386ced42e5a31c7004523a9d99dc85c1" +checksum = "418d98c3dce08f0d13526692bbd1888bff4c5f45207f5093d168b5a224fbc88c" dependencies = [ "cranelift-codegen", "libc", @@ -915,9 +915,9 @@ dependencies = [ [[package]] name = "cranelift-srcgen" -version = "0.136.4" +version = "0.136.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b533db1679b4b728568bd490da6533cf50974c48a4d7534e78f1e0e02bc39769" +checksum = "4f016b9994e39cad68107f33fa5b8dcee8fb7e38332bde3f97690e5e8abe1b10" [[package]] name = "crc32fast" @@ -2379,9 +2379,9 @@ dependencies = [ [[package]] name = "pulley-interpreter" -version = "49.0.4" +version = "49.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cd3de43f4f4cff536de8902097edbdf749321b97dda548f36a37918eb85bba3" +checksum = "1d74df9e20cba0f6dd07f01c3140535a6b23e40aecccfcacfb921621b41d0acb" dependencies = [ "cranelift-bitset", "log", @@ -2391,9 +2391,9 @@ dependencies = [ [[package]] name = "pulley-macros" -version = "49.0.4" +version = "49.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8319a8fa5269b4895957f8795861c42822bd26218e495f70060a963323d94847" +checksum = "8415513bfa407c06de6f11dbe9437f2352e075a5520ff491a131166d3d90aed1" dependencies = [ "proc-macro2", "quote", @@ -3670,7 +3670,7 @@ dependencies = [ [[package]] name = "tw-api" -version = "0.86.0" +version = "0.87.0" dependencies = [ "serde", "serde_json", @@ -3681,7 +3681,7 @@ dependencies = [ [[package]] name = "tw-bedrock" -version = "0.86.0" +version = "0.87.0" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -3702,7 +3702,7 @@ dependencies = [ [[package]] name = "tw-breaker" -version = "0.86.0" +version = "0.87.0" dependencies = [ "serde", "serde_json", @@ -3710,7 +3710,7 @@ dependencies = [ [[package]] name = "tw-config" -version = "0.86.0" +version = "0.87.0" dependencies = [ "blake3", "libc", @@ -3736,7 +3736,7 @@ dependencies = [ [[package]] name = "tw-control" -version = "0.86.0" +version = "0.87.0" dependencies = [ "axum", "base64 0.22.1", @@ -3782,7 +3782,7 @@ dependencies = [ [[package]] name = "tw-dialect" -version = "0.86.0" +version = "0.87.0" dependencies = [ "memchr", "serde", @@ -3791,7 +3791,7 @@ dependencies = [ [[package]] name = "tw-engine" -version = "0.86.0" +version = "0.87.0" dependencies = [ "chrono", "serde", @@ -3805,7 +3805,7 @@ dependencies = [ [[package]] name = "tw-gateway" -version = "0.86.0" +version = "0.87.0" dependencies = [ "arc-swap", "async-stream", @@ -3856,7 +3856,7 @@ dependencies = [ [[package]] name = "tw-guard" -version = "0.86.0" +version = "0.87.0" dependencies = [ "aho-corasick", "base64 0.22.1", @@ -3875,7 +3875,7 @@ dependencies = [ [[package]] name = "tw-handover" -version = "0.86.0" +version = "0.87.0" dependencies = [ "libc", "serde", @@ -3888,7 +3888,7 @@ dependencies = [ [[package]] name = "tw-link" -version = "0.86.0" +version = "0.87.0" dependencies = [ "serde", "serde_json", @@ -3902,7 +3902,7 @@ dependencies = [ [[package]] name = "tw-observe" -version = "0.86.0" +version = "0.87.0" dependencies = [ "tokio", "tracing", @@ -3911,7 +3911,7 @@ dependencies = [ [[package]] name = "tw-pack" -version = "0.86.0" +version = "0.87.0" dependencies = [ "age", "blake3", @@ -3935,7 +3935,7 @@ dependencies = [ [[package]] name = "tw-plugin" -version = "0.86.0" +version = "0.87.0" dependencies = [ "libc", "rand 0.10.2", @@ -3951,7 +3951,7 @@ dependencies = [ [[package]] name = "tw-pricing" -version = "0.86.0" +version = "0.87.0" dependencies = [ "arc-swap", "flate2", @@ -3964,14 +3964,14 @@ dependencies = [ [[package]] name = "tw-secret" -version = "0.86.0" +version = "0.87.0" dependencies = [ "thiserror 2.0.20", ] [[package]] name = "tw-store" -version = "0.86.0" +version = "0.87.0" dependencies = [ "blake3", "bytes", @@ -3991,7 +3991,7 @@ dependencies = [ [[package]] name = "tw-sync" -version = "0.86.0" +version = "0.87.0" dependencies = [ "age", "axum", @@ -4019,7 +4019,7 @@ dependencies = [ [[package]] name = "tw-types" -version = "0.86.0" +version = "0.87.0" dependencies = [ "serde", "serde_json", @@ -4028,7 +4028,7 @@ dependencies = [ [[package]] name = "tw-watch" -version = "0.86.0" +version = "0.87.0" dependencies = [ "libc", "notify", @@ -4039,7 +4039,7 @@ dependencies = [ [[package]] name = "tw-yaml" -version = "0.86.0" +version = "0.87.0" dependencies = [ "saphyr-parser", "serde_yaml_ng", @@ -4049,7 +4049,7 @@ dependencies = [ [[package]] name = "twcore" -version = "0.86.0" +version = "0.87.0" dependencies = [ "anyhow", "axum", @@ -4363,9 +4363,9 @@ dependencies = [ [[package]] name = "wasmtime" -version = "49.0.4" +version = "49.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36abb25de5518707e5ba96b586366b09db168d2d4976cb9dac57265a317497ea" +checksum = "d45bf3b13781b9febb0eef42e1e145146c92ef891f345fd918e0e0fa1568c992" dependencies = [ "addr2line", "async-trait", @@ -4402,9 +4402,9 @@ dependencies = [ [[package]] name = "wasmtime-environ" -version = "49.0.4" +version = "49.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92f16a4d09fb1fbf2dcfeddb9818e1517a258e13b505b3bfdadcf47a19bf31ca" +checksum = "833f00f3dc6128159284c5b71e211f24c97d45e052230a14f767fd830a6fb8b0" dependencies = [ "anyhow", "cpp_demangle", @@ -4433,15 +4433,15 @@ dependencies = [ [[package]] name = "wasmtime-internal-component-util" -version = "49.0.4" +version = "49.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4006009fc3598af4e895ab18c6c33d2c6b7c657b1bd0a755ca7ee1abef6f0c16" +checksum = "b168d39f2d6b896d89fe096afa4ccdd7a8e927b01469b590af84f02fca3126a1" [[package]] name = "wasmtime-internal-core" -version = "49.0.4" +version = "49.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "947a732ad659f42c10ec639706fc8cb1f068dd9a55eebde8a1f24dfd06dfa525" +checksum = "7ad86a65d12c6a9202584ceb658c9a20cf5b25c32eed61c9be5e5f863f88f0d9" dependencies = [ "hashbrown 0.17.1", "libm", @@ -4450,9 +4450,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-cranelift" -version = "49.0.4" +version = "49.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbfb8b455d524939e03df42e3e749f8e26e26ba2acd8b08fa00b3bda55686750" +checksum = "a71f0fd1dd40e4e18d4a20cdba29ff8e0eb2c0f21b12c2eb5869e0fea789dd05" dependencies = [ "cranelift-codegen", "cranelift-control", @@ -4476,9 +4476,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-fiber" -version = "49.0.4" +version = "49.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd8aa8711f68b2cdd94c2b85ba5c8317edaa1ac4ee0a0534b957123680239307" +checksum = "fa4dfa39e58735b24ce3d28bfc8c2b3041603cc8a941bb268395351beff90172" dependencies = [ "cc", "libc", @@ -4490,9 +4490,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-debug" -version = "49.0.4" +version = "49.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e873e4504faf50aa8ce255d6ea7210d87b39d5f379c2486782e2c5f179c70ca8" +checksum = "f2eb98d4486c7b0163447d77dea06f3cc87e5e5ba5fc93613f8ec42e27ea181d" dependencies = [ "cc", "wasmtime-internal-versioned-export-macros", @@ -4500,9 +4500,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-icache-coherence" -version = "49.0.4" +version = "49.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197ab1fda5da958a5c0b8a97eb241beafc48ac568b82ef4f12f38d6866c74173" +checksum = "852cae234735d79db6c58fe0d04b32f4078ad01fdb3fd6d488ac366132ba9785" dependencies = [ "libc", "wasmtime-internal-core", @@ -4511,9 +4511,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-unwinder" -version = "49.0.4" +version = "49.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5577f3a5a979a7c3b1b23c1e9f13e70a8583acc8828471ec56f282117fc4952b" +checksum = "b9289378c82b9db7e7cf933d6bb441ad968536ac48f3f38c55b7cf9d3e8d87e1" dependencies = [ "cranelift-codegen", "log", @@ -4523,9 +4523,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-versioned-export-macros" -version = "49.0.4" +version = "49.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6399c9c4050ed431cf66e21c05fadd6f9731db44476f452f8c7142b4597b883" +checksum = "cc5c60c732d99dbcc5d670b71ba6ccf2b015cd16fc828990eaad2c4b8805ebdb" dependencies = [ "proc-macro2", "quote", @@ -4534,9 +4534,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-winch" -version = "49.0.4" +version = "49.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f1b2b35e5260f0d0d0cd7f789893515894cd407650b5ab4b1ccbf06c9ecb90" +checksum = "c236aa2967d9ed27babc3a76d478f6323cb5bf4c77d20102e282828226646458" dependencies = [ "cranelift-codegen", "gimli", @@ -4589,9 +4589,9 @@ dependencies = [ [[package]] name = "winch-codegen" -version = "49.0.4" +version = "49.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b6796e6383dadb97ecfe9a6685bdfe3c48bad1b9d3627420ed874fa0b424787" +checksum = "98e378f1851ec513f230e3aa1b77a3b4c2e0ebed8db0e551b0c51144f43c133d" dependencies = [ "cranelift-assembler-x64", "cranelift-codegen", diff --git a/Cargo.toml b/Cargo.toml index fb376381..3f5cb7dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,7 @@ categories = ["network-programming", "web-programming::http-server"] # 二进制走 CalVer,crate 走 SemVer —— 两者是两回事,卖给不同的人。 # 这里是 crate 的版本。 -version = "0.86.0" +version = "0.87.0" [workspace.dependencies] # ── 内部 crate ─────────────────────────────────────────── diff --git a/crates/tw-config/tests/manual/schema.rs b/crates/tw-config/tests/manual/schema.rs index 5f52abf0..f442d607 100644 --- a/crates/tw-config/tests/manual/schema.rs +++ b/crates/tw-config/tests/manual/schema.rs @@ -736,8 +736,8 @@ pub fn sections() -> Vec
{ Kind::Obj("providers[].min_speed"), Def::Unset, t( - "End a streamed answer that generates too slowly: from the first token on, once a second, when the average speed over the last `window_secs` is below `tokens_per_sec`, the answer ends with an error clients retry (503, `overloaded_error` for Anthropic clients), and the upstream counts one failure toward `failover.failures_to_pause`. Pauses of 5 seconds or more partway through are left out of the average; the idle timeout deals with them. Answers that are not streamed are not judged. Unset: never.", - "流式回答生成得太慢时结束它:从第一个 token 起每秒看一次,最近 `window_secs` 秒的平均速度低于 `tokens_per_sec` 时,回答以一条客户端会重试的错误结束(503,Anthropic 格式的客户端收到 `overloaded_error`),这家记一次失败,计入 `failover.failures_to_pause`。中途 5 秒以上的停顿不算进平均速度,由无响应超时处理。不是流式的回答不管。不写:不管。", + "End a streamed answer that generates too slowly: from the first token on, once a second, when the average speed over the last `window_secs` is below `tokens_per_sec`, the answer ends with an error clients retry (503, `overloaded_error` for Anthropic clients), and the upstream counts one failure toward `failover.failures_to_pause`. Long pauses and summarized reasoning partway through are left out of the average; the idle timeout deals with pauses. Answers that are not streamed are not judged. Unset: never.", + "流式回答生成得太慢时结束它:从第一个 token 起每秒看一次,最近 `window_secs` 秒的平均速度低于 `tokens_per_sec` 时,回答以一条客户端会重试的错误结束(503,Anthropic 格式的客户端收到 `overloaded_error`),这家记一次失败,计入 `failover.failures_to_pause`。中途较长的停顿和摘要式的推理不算进平均速度,停顿由无响应超时处理。不是流式的回答不管。不写:不管。", ), ), row( diff --git a/crates/tw-plugin/Cargo.toml b/crates/tw-plugin/Cargo.toml index 7b228ee6..fa9c35f9 100644 --- a/crates/tw-plugin/Cargo.toml +++ b/crates/tw-plugin/Cargo.toml @@ -18,7 +18,7 @@ description = "Runs one script plugin's hooks inside a WebAssembly sandbox (Quic # **只有运行时**:没有 Cranelift,没有池分配器。沙箱的机器码在 build.rs 里预编译 # 好、嵌进二进制。版本钉死,并且和下面构建依赖里的是同一个:预编译产物只肯在 # 同一版本的 Wasmtime 里加载 -wasmtime = { version = "=49.0.4", default-features = false, features = ["runtime", "std"] } +wasmtime = { version = "=49.0.5", default-features = false, features = ["runtime", "std"] } serde = { workspace = true } serde_json = { workspace = true } sha2 = { workspace = true } @@ -36,7 +36,7 @@ windows-sys = { workspace = true, features = ["Win32_Foundation", "Win32_System_ [build-dependencies] # 构建时的编译器:Cranelift 把快照编成目标平台的机器码;all-arch 让交叉编译 # (比如在 x64 上编 aarch64 的 Windows)也能编 -wasmtime = { version = "=49.0.4", default-features = false, features = ["runtime", "std", "cranelift", "all-arch", "parallel-compilation"] } +wasmtime = { version = "=49.0.5", default-features = false, features = ["runtime", "std", "cranelift", "all-arch", "parallel-compilation"] } # 改写快照的数据段。和 Wasmtime 49 自己用的是同一版 wasmparser = "0.258" wasm-encoder = "0.258" diff --git a/docs/config.md b/docs/config.md index 474819c0..65aaa8dd 100644 --- a/docs/config.md +++ b/docs/config.md @@ -441,7 +441,7 @@ Upstreams: the APIs requests are forwarded to. | `pricing` | string | — | Name of a price sheet under `pricing.sheets`. Unset: the default price table. | | `model_specs` | map of model id → [`providers[].model_specs.*`](#cfg-providers-model_specs) | `{}` | Context window, output limit, reasoning and image input of single models of this upstream, written by hand, by exact model id. They take precedence over the price table: for models it does not know, or gets wrong. | | `max_concurrent` | integer | — | Most requests sent to this upstream at the same time, from 1 to 1000. When it is full, a conversation that stays on it waits for a free slot and other requests go to the next upstream; see `failover.wait_secs`. Unset: no limit. | -| `min_speed` | object, [`providers[].min_speed`](#cfg-providers-min_speed) | — | End a streamed answer that generates too slowly: from the first token on, once a second, when the average speed over the last `window_secs` is below `tokens_per_sec`, the answer ends with an error clients retry (503, `overloaded_error` for Anthropic clients), and the upstream counts one failure toward `failover.failures_to_pause`. Pauses of 5 seconds or more partway through are left out of the average; the idle timeout deals with them. Answers that are not streamed are not judged. Unset: never. | +| `min_speed` | object, [`providers[].min_speed`](#cfg-providers-min_speed) | — | End a streamed answer that generates too slowly: from the first token on, once a second, when the average speed over the last `window_secs` is below `tokens_per_sec`, the answer ends with an error clients retry (503, `overloaded_error` for Anthropic clients), and the upstream counts one failure toward `failover.failures_to_pause`. Long pauses and summarized reasoning partway through are left out of the average; the idle timeout deals with pauses. Answers that are not streamed are not judged. Unset: never. | | `limits` | list of [`providers[].limits[]`](#cfg-providers-limits) | — | Usage limits of this upstream, written like its own quota: requests, tokens or cost every few minutes or hours, per day, week or month. Every one has to have room before a request is sent to it. When a rolling window is full, requests go to the next upstream or wait for room; when a day, week or month is used up, requests go to the next upstream until the next one starts. Unset: no limit. | | `disabled` | bool | `false` | Take the upstream out of routing and out of the model list, and keep its configuration. | | `balance` | `auto` \| `off` \| `openrouter` \| `deepseek` \| `moonshot` \| `sub2api` \| `newapi` \| `thinkwatch` | `auto` | Where the upstream's balance is read from, with its own key and proxy: `openrouter`, `deepseek`, `moonshot`, `sub2api`, `newapi` (New API and One API) or `thinkwatch` (a ThinkWatch Enterprise gateway). `auto`: recognized from `base_url` for OpenRouter, DeepSeek and Moonshot; any other address, except Anthropic's, OpenAI's and Gemini's own, is asked once whether it is a Sub2API, ThinkWatch Enterprise or New API relay, and the answer is kept in memory only. `off`: no balance. ChatGPT and Z.ai accounts and Bedrock have no balance whatever is written here. | diff --git a/docs/config.zh-CN.md b/docs/config.zh-CN.md index 718f4987..2b233f66 100644 --- a/docs/config.zh-CN.md +++ b/docs/config.zh-CN.md @@ -313,7 +313,7 @@ clients: | `pricing` | 字符串 | — | `pricing.sheets` 中某张价目表的名字。不写:默认价目表。 | | `model_specs` | 映射: 模型 ID → [`providers[].model_specs.*`](#cfg-providers-model_specs) | `{}` | 手写这家上游某些模型的上下文窗口、输出上限、会不会推理、收不收图,按模型 ID 完全匹配。写了就优先于价目表,用于价目表里没有或写错的模型。 | | `max_concurrent` | 整数 | — | 同时发给这家的请求最多几个,取值 1 到 1000。满了的时候,留在这家的对话等空位,别的请求换下一家;等多久见 `failover.wait_secs`。不写:不限。 | -| `min_speed` | 对象,见 [`providers[].min_speed`](#cfg-providers-min_speed) | — | 流式回答生成得太慢时结束它:从第一个 token 起每秒看一次,最近 `window_secs` 秒的平均速度低于 `tokens_per_sec` 时,回答以一条客户端会重试的错误结束(503,Anthropic 格式的客户端收到 `overloaded_error`),这家记一次失败,计入 `failover.failures_to_pause`。中途 5 秒以上的停顿不算进平均速度,由无响应超时处理。不是流式的回答不管。不写:不管。 | +| `min_speed` | 对象,见 [`providers[].min_speed`](#cfg-providers-min_speed) | — | 流式回答生成得太慢时结束它:从第一个 token 起每秒看一次,最近 `window_secs` 秒的平均速度低于 `tokens_per_sec` 时,回答以一条客户端会重试的错误结束(503,Anthropic 格式的客户端收到 `overloaded_error`),这家记一次失败,计入 `failover.failures_to_pause`。中途较长的停顿和摘要式的推理不算进平均速度,停顿由无响应超时处理。不是流式的回答不管。不写:不管。 | | `limits` | 对象列表,见 [`providers[].limits[]`](#cfg-providers-limits) | — | 这家上游的用量上限,照着它自己的限额写:每几分钟、每几小时、每天、每周或每月的请求数、token 数或费用。每一条都有余量才往这家发。滚动窗口满了,请求换下一家或者等它空出来;天、周、月用满了,到下一期之前请求换下一家。不写:不限。 | | `disabled` | 布尔 | `false` | 不参与路由,模型也不出现在模型列表里;配置原样保留。 | | `balance` | `auto` \| `off` \| `openrouter` \| `deepseek` \| `moonshot` \| `sub2api` \| `newapi` \| `thinkwatch` | `auto` | 从哪里读这家上游的余额,用它自己的密钥、走它自己的代理:`openrouter`、`deepseek`、`moonshot`、`sub2api`、`newapi`(New API 和 One API)或 `thinkwatch`(ThinkWatch 企业网关)。`auto`:OpenRouter、DeepSeek、Moonshot 按 `base_url` 识别;其他地址(Anthropic、OpenAI、Gemini 的官方地址除外)询问一次它是不是 Sub2API、ThinkWatch 企业网关或 New API 中转站,结果只保存在内存中。`off`:不读余额。ChatGPT、Z.ai 账号和 Bedrock 不读余额,与这里写什么无关。 | diff --git a/release-notes/0.87.0.md b/release-notes/0.87.0.md new file mode 100644 index 00000000..9c75f25b --- /dev/null +++ b/release-notes/0.87.0.md @@ -0,0 +1,22 @@ +This release lets an upstream set a minimum generation speed: a streamed answer that keeps generating below it is ended with a retryable error, so the client sends the request again. + +**Upgrade notes** + +- The control-plane protocol version goes from 57 to 58. +- The request store's schema is unchanged (28); nothing is cleared. +- New configuration key `providers[].min_speed`; existing configurations are valid as they are. +- New message codes: `config.min_speed_range`, `gw.upstream.too_slow`. + +**Minimum generation speed.** Some self-hosted servers occasionally drop to a few tokens per second partway through an answer and recover when the request is sent again. Neither the idle timeout nor failover covers this: content is still arriving, and it has already reached the client. + +- `providers[].min_speed: { tokens_per_sec, window_secs }` turns it on for one upstream: `tokens_per_sec` from 1 to 1000, `window_secs` from 5 to 120 (20 when left out). Unset means off. +- From the first token on, a streamed answer (SSE or Bedrock eventstream) is judged once a second on its average speed over the last `window_secs` seconds, estimated the same way as the live speed. +- Pauses are not judged: nothing is decided while no content has arrived for over a second, and silences of 5 seconds or more (a quarter of the window when it is under 20 seconds) are taken out of the window and left to the idle timeout. Reasoning that arrives as a summary (Anthropic, Gemini, Bedrock, the Responses reasoning summary) is not judged either; raw reasoning such as Chat `reasoning_content` is. Judging stops once the answer has ended. +- Non-streamed answers, WebSocket turns and Gemini streams requested without `alt=sse` are not judged. +- Below the minimum, the answer ends with a 503 error in the client's format (Anthropic `overloaded_error`, Responses `server_error`, Gemini `UNAVAILABLE`) and the upstream connection is dropped. Claude Code and Codex resend the request on their own. +- The request fails with `gw.upstream.too_slow`, keeps the measured speed (also on `RequestFailed.tokens_per_sec`) and the usage generated so far. The upstream records one failure and the conversation stops sticking to it for that turn, so a resent request prefers another candidate. Upstream checkup and `/token-rate` medians leave out failed requests. +- `ProviderView` and `ProviderInput` carry `min_speed`; config packs, sharing and sync carry it with the upstream. One-click repair drops `min_speed` when the rate is out of range and resets `window_secs` to 20 when the window is. + +**Gemini live speed.** The streamed output count no longer includes hidden reasoning (`thoughtsTokenCount`), which made the live speed read 0 until the text estimate caught up. + +**Security.** The plugin sandbox's Wasmtime is updated to 49.0.5 for RUSTSEC-2026-0422, RUSTSEC-2026-0423 and RUSTSEC-2026-0424.