Probe sends a minimal hello chat just to harvest response headers in one round trip: status validates the URL/model/key combo, X-Context-Window gives the live size, X-Budget-Remaining the live fraction. The body is closed unread; on the cloud proxy that may already charge one token, the cost of a si
(parent context.Context)
| 262 | // atomic.Bool: Probe and Chat race on the same Client (startup probe still |
| 263 | // in flight when the first turn fires) and both read it via postChat; Chat |
| 264 | // may also write it. A plain bool would be a data race. |
| 265 | noReasoningEffort atomic.Bool |
| 266 | } |
| 267 | |
| 268 | // New builds a Client governed by the caller's context, not http.Client.Timeout. |
| 269 | // That timeout is end-to-end (it covers body reads) and would kill a slow but |
| 270 | // legitimate SSE stream mid-flight on slow local backends. tui.Model's turnCtx |
| 271 | // is the single cancellation source; connect-level safety (DNS/TCP) is already |
| 272 | // bounded by Go's default Dialer (30s). |
| 273 | func New(base, model, token string) *Client { |
| 274 | return &Client{ |
| 275 | BaseURL: strings.TrimRight(base, "/"), |
| 276 | Model: model, |
| 277 | Token: token, |
| 278 | HTTP: &http.Client{}, |
| 279 | IdleTimeout: idleTimeoutFromEnv(), |
| 280 | RetryBackoff: defaultRetryBackoff, |
| 281 | } |