doPost performs one round-trip, mapping status into the typed cloud errors Probe and sendChat share. On 200 it returns the live response with body open for streaming; on non-200 the body is drained and closed first. Budget is set only on 402. errBody returns the raw body on a non-2xx other than 401/
(parent context.Context, body chatRequest)
| 399 | Messages: toWire(msgs), |
| 400 | Tools: tools, |
| 401 | Stream: true, |
| 402 | StreamOptions: &streamOptions{IncludeUsage: true}, |
| 403 | ReasoningEffort: "high", |
| 404 | }) |
| 405 | if err != nil { |
| 406 | return nil, &Event{Kind: EventError, Err: err, Budget: budget} |
| 407 | } |
| 408 | return resp, nil |
| 409 | } |
| 410 | |
| 411 | // postChat dispatches via doPost; on a 400 rejecting reasoning it drops |
| 412 | // reasoning_effort for this Client's lifetime and retries once. Two wild |
| 413 | // flavours, both caught by substring match: newer OpenAI models |
| 414 | // ("reasoning_effort … not supported") and Ollama non-thinking models |
| 415 | // ("<model> does not support thinking"). Each signal is the provider's own |
| 416 | // phrase ("not support"+"reasoning_effort", or the literal "does not support |
| 417 | // thinking") so an unrelated 400 that merely contains the word "thinking" |
| 418 | // can't trip the fallback and latch reasoning off for the Client's whole life. |
| 419 | // Probe never sets ReasoningEffort, so its 400 can't trip the flag. |
| 420 | func (c *Client) postChat(parent context.Context, body chatRequest) (*http.Response, cloud.BudgetStatus, error) { |
| 421 | if c.noReasoningEffort.Load() { |
| 422 | body.ReasoningEffort = "" |
| 423 | } |
| 424 | resp, budget, errBody, err := c.doPost(parent, body) |
| 425 | if err != nil && body.ReasoningEffort != "" && |
| 426 | ((bytes.Contains(errBody, []byte("not support")) && |
| 427 | bytes.Contains(errBody, []byte("reasoning_effort"))) || |
| 428 | bytes.Contains(errBody, []byte("does not support thinking"))) { |
| 429 | c.noReasoningEffort.Store(true) |
| 430 | body.ReasoningEffort = "" |
| 431 | resp, budget, _, err = c.doPost(parent, body) |
| 432 | } |
| 433 | return resp, budget, err |
| 434 | } |
| 435 | |
| 436 | // doPost performs one round-trip, mapping status into the typed cloud errors |
| 437 | // Probe and sendChat share. On 200 it returns the live response with body open |
| 438 | // for streaming; on non-200 the body is drained and closed first. Budget is set |
| 439 | // only on 402. errBody returns the raw body on a non-2xx other than 401/402, so |
| 440 | // postChat can check it for the reasoning_effort fallback signal without |
| 441 | // re-reading. |
no test coverage detected