Skip to content

feat: remove headers from client, move to CallResponse for public api#197

Merged
dongri merged 2 commits intodongri:mainfrom
MichaelProjects:MichaelProjects/remove-mut-client
Mar 29, 2026
Merged

feat: remove headers from client, move to CallResponse for public api#197
dongri merged 2 commits intodongri:mainfrom
MichaelProjects:MichaelProjects/remove-mut-client

Conversation

@MichaelProjects
Copy link
Copy Markdown

Hey, I took another spin on #168

The previous PR was declined primarily because it removed the header entirely #170 — this change addresses that by moving headers out of the client and into the response itself via CallResponse, keeping them co-located with the data they describe.

Beyond headers, the &mut self pattern has a real concurrency cost: it prevents OpenAIClient from being Sync, meaning it cannot be shared across threads without a Mutex. But even with a Mutex, concurrent requests become serialized — and if multiple requests do run concurrently, response_headers on the client becomes last-write-wins, meaning you can no longer reliably tell which request a given header came from. Moving headers into CallResponse eliminates this entirely — each response owns its headers, scoped to that call.

This PR fixes both:

  • Response headers are accessible directly on the returned CallResponse via .headers
  • All public methods now use &self, making OpenAIClient shareable across threads via Arc
  #[derive(Debug, Clone)]
  pub struct CallResponse<T> {
      pub headers: HeaderMap,
      pub inner: T,
  }

The added wrapper is a small ergonomic cost, but it keeps headers and response data together without any shared mutable state on the client.

Breaking change
This would be a breaking change to the api so a major release needed but it will definitely make the library more easy and performance to use for concurrent use cases.

@MichaelProjects MichaelProjects changed the title remove need for mutable client add CallResponse to function calls feat: remove need for mutable client add CallResponse to function calls Mar 28, 2026
@dongri dongri requested a review from Copilot March 29, 2026 11:29
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the OpenAIClient request APIs to remove the need for mutable borrows by returning response headers alongside decoded response bodies via a new CallResponse<T> wrapper, improving thread-shareability and concurrency ergonomics.

Changes:

  • Introduces CallResponse<T> { headers, inner } to carry per-call headers with the response payload.
  • Updates OpenAIClient internal helpers (get/post/delete/post_form) and all public API methods to take &self and return Result<CallResponse<T>, APIError>.
  • Updates examples to use non-mutable clients and access payload data via .inner (and headers via .headers).

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 4 comments.

File Description
src/v1/responses/responses.rs Adds the new CallResponse<T> wrapper type.
src/v1/audio.rs Removes per-response header storage from AudioSpeechResponse (headers now come from CallResponse).
src/v1/api.rs Switches all client methods to &self and returns CallResponse<T> from non-stream API calls.
examples/*.rs Updates example clients to be non-mutable and uses result.inner/result.headers.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/v1/api.rs Outdated
Comment thread src/v1/api.rs
Comment on lines +259 to +262
Ok(parsed) => Ok(CallResponse {
headers,
inner: parsed,
}),
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OpenAIClient::response_headers is no longer updated anywhere (the assignment was removed from handle_response), so it will always remain None even after successful calls. Since the new CallResponse<T> exposes headers per-call, this field should either be removed (breaking but consistent) or kept in sync (e.g., set it on success) to avoid a silently broken public field.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh damn, got it, removed.

Comment thread src/v1/api.rs Outdated
Comment on lines +9 to +13
#[derive(Debug, Clone)]
pub struct CallResponse<T> {
pub headers: HeaderMap,
pub inner: T,
}
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CallResponse<T> is a generic wrapper used by many API endpoints (completions, files, models, etc.), but it’s defined under v1::responses::responses, which makes the public API surface confusing and couples unrelated modules to the Responses API module path. Consider moving CallResponse to a more central module (e.g., v1::api, v1::common, or a new v1::response module) and re-exporting it from there.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

@MichaelProjects MichaelProjects Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dongri not sure about this one tbh, would be up to you to decide. In my world I would keep it with the other respones to just have a clean definition where responses are live.

@dongri
Copy link
Copy Markdown
Owner

dongri commented Mar 29, 2026

@MichaelProjects Could you please review the issues raised by Copilot?

@MichaelProjects MichaelProjects changed the title feat: remove need for mutable client add CallResponse to function calls feat: remove headers form client, move to CallResponse for public api Mar 29, 2026
@MichaelProjects MichaelProjects changed the title feat: remove headers form client, move to CallResponse for public api feat: remove headers from client, move to CallResponse for public api Mar 29, 2026
@dongri dongri merged commit daa29c8 into dongri:main Mar 29, 2026
1 check passed
@dongri
Copy link
Copy Markdown
Owner

dongri commented Mar 29, 2026

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants