-
Notifications
You must be signed in to change notification settings - Fork 5.3k
feat: introduce responses-api-proxy #4246
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
for header in req.headers() { | ||
let name_ascii = header.field.as_str(); | ||
let name = name_ascii.as_str(); | ||
if name.eq_ignore_ascii_case("authorization") { | ||
continue; | ||
} | ||
let header_name = match HeaderName::from_bytes(name.as_bytes()) { | ||
Ok(name) => name, | ||
Err(_) => continue, | ||
}; | ||
if let Ok(value) = HeaderValue::from_bytes(header.value.as_bytes()) { | ||
headers.append(header_name, value); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Preserve client header names when forwarding
The proxy rebuilds the upstream header map by calling HeaderName::from_bytes(name.as_bytes())
for each incoming header. HeaderName
only accepts lowercase names, so canonical headers like Content-Type
, OpenAI-Beta
, or User-Agent
—which most clients send capitalised—are skipped with the Err(_) => continue
branch. Forwarded requests therefore lack required metadata (e.g. Content-Type: application/json
), and OpenAI will respond with 4xx errors even though the client supplied it. Normalize the header names to lowercase (or use HeaderName::from_str
after to_ascii_lowercase
) before inserting so the proxy preserves all client headers.
Useful? React with 👍 / 👎.
4b50983
to
2ea027f
Compare
4bc5389
to
97d4ac4
Compare
fs::create_dir_all(parent).with_context(|| format!("create_dir_all {parent_display}"))?; | ||
} | ||
let info = ServerInfo { port }; | ||
let data = serde_json::to_vec(&info).context("serialize startup info")?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we get automatic backtraces somehow? context
looks so busy
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, no, but I'm happy to drop the context()
stuff and make this process less likely to spill information.
Details are in
responses-api-proxy/README.md
, but the key contribution of this PR is a new subcommand,codex responses-api-proxy
, which reads the auth token for use with the OpenAI Responses API fromstdin
at startup and then proxiesPOST
requests to/v1/responses
over tohttps://api.openai.com/v1/responses
, injecting the auth token as part of theAuthorization
header.The expectation is that
codex responses-api-proxy
is launched by a privileged user who has access to the auth token so that it can be used by unprivileged users of the Codex CLI on the same host.If the client only has one user account with
sudo
, one option is to:sudo codex responses-api-proxy --http-shutdown --server-info /tmp/server-info.json
to start the server/tmp/server-info.json
sudo
privileges (which is irreversible!) like so:codex
with the proxy (seeREADME.md
)GET
request to the server using thePORT
fromserver-info.json
to shut it down:curl --fail --silent --show-error "http://127.0.0.1:$PORT/shutdown"
To protect the auth token, we:
"Bearer "
into it to startstdin
, copying to the contents into the buffer after the prefixString
from that buffer (so the data is now on the heap).leak()
on theString
so we can treat its contents as a&'static str
, as it will live for the rest of the processsmlock(2)
the memory backing the&'static str
&'static str
when building an HTTP request, we useHeaderValue::from_static()
to avoid copying the&str
.set_sensitive(true)
on theHeaderValue
, which in theory indicates to other parts of the HTTP stack that the header should be treated with "special care" to avoid leakage:https://github.com/hyperium/http/blob/439d1c50d71e3be3204b6c4a1bf2255ed78e1f93/src/header/value.rs#L346-L376