1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
//! Timeout flag resolution (#1688).
//!
//! Every timeout flag resolves through one chain:
//!
//! ```text
//! [providers.<name>] -> [agent] -> the family's compiled default
//! ```
//!
//! Before this, `timeout_secs` and `stream_idle_timeout_secs` were read in
//! exactly one place — `factory.rs`, inside `configure_openai_compatible` — and
//! against exactly one tier: the per-provider struct. A key set under `[agent]`
//! was parsed, stored, and silently ignored, and the two non-compat families
//! (`anthropic`, `gemini`) ignored both keys at every tier (#1689). The same
//! flag therefore meant three different things depending on which family a
//! provider happened to belong to, which is the shape of bug #1687 was filed
//! against.
//!
//! The resolution is deliberately pure: it takes the two configured values and
//! the family's compiled floor and returns what applies plus which tier said
//! so. No `Config::current()`, no disk, no globals — so precedence is testable
//! without a config file anywhere. The callers in `factory.rs` are the thin
//! edge that feeds it real values.
use Duration;
/// Which tier supplied an effective timeout value.
/// A resolved timeout flag: the value that applies, and where it came from.
/// Resolve one timeout flag across the three tiers.
///
/// A configured `0` is skipped, not honoured. Every existing call site already
/// did this (`filter(|&s| s > 0)`), and for good reason: under reqwest a
/// zero-second request timeout is an instant deadline, so `timeout_secs = 0`
/// read literally turns a typo into "every call fails" instead of "the default
/// applies". Skipping is the safe direction — but skipping *silently* is not, so
/// the skipped tiers are reported on the result for the caller to warn about.
///
/// `compiled_default` is the family's own floor: `Some(300)` for the
/// non-streaming ceiling every family shares (`DEFAULT_TIMEOUT`), or `None` for
/// a flag whose default is decided at runtime.
///
/// ```text
/// resolve_timeout(Some(120), Some(60), Some(300)) -> 120 (Provider)
/// resolve_timeout(None, Some(60), Some(300)) -> 60 (Agent)
/// resolve_timeout(None, None, Some(300)) -> 300 (Default)
/// resolve_timeout(Some(0), None, Some(300)) -> 300 (Default, provider_zero)
/// resolve_timeout(None, None, None) -> None (Default, defer to runtime)
/// ```
/// A configured value that can actually act as a timeout: present, and not `0`.
/// Resolve the thinking-loop guard: `[providers.<name>]` then `[agent]`.
///
/// This clock is NOT resolved by [`resolve_timeout`], and the difference is the
/// point. The transport ceilings treat `0` as an unusable value because a
/// zero-second reqwest timeout is an instant deadline: reading `0` literally
/// turns a typo into "every call fails". The thinking-loop guard is a prompt to
/// the model, not a deadline on a connection, and `0` is its documented
/// off-switch ("Set to 0 to disable", `config/types.rs`). Skipping a `0` here
/// would take away the only way to silence the guard for one provider, so the
/// configured value is honoured verbatim.
///
/// The `[agent]` tier is a plain `u64` with a serde default of 600, so it is
/// never absent: the chain is two tiers, not three.
///
/// ```text
/// resolve_thinking_loop(Some(900), 600) -> 900 (provider wins)
/// resolve_thinking_loop(None, 600) -> 600 (agent applies)
/// resolve_thinking_loop(Some(0), 600) -> 0 (disabled, on purpose)
/// resolve_thinking_loop(None, 0) -> 0 (disabled globally)
/// ```