Summary
Multipart uploads currently build the entire request body in memory before sending it. That means a large file upload can turn into a large memory spike, even though the file-handling path uses reader-backed values that look like they should stream.
I checked this on main at 9eb2084.
What I found
Why this matters
For endpoints that accept large files, the CLI can end up holding the full encoded multipart request in memory. That can make uploads slower than necessary, increase RSS significantly, or fail in constrained environments. There is also a small file descriptor leak risk because the upload file is opened for encoding but not closed by the normal request path.
Possible fix
A good direction would be to stream multipart encoding with io.Pipe, so file contents are copied into the HTTP request as the client reads the body. The writer side should close owned upload files after each file reader is copied, while preserving the existing stdin behavior for @- / - inputs.
One wrinkle: the current bytes.Buffer body is replayable by the Go SDK for retries. A plain streaming pipe body would not be replayable, so the fix should make that behavior explicit. A few options:
- Disable automatic retries for streamed multipart bodies.
- Add a replayable multipart body that can reopen owned files for each retry.
- Spool the encoded multipart body to a temporary file when replayability is required.
The second option is probably the most complete long-term fix. The first option may be enough for a smaller CLI-side change if the retry tradeoff is acceptable.
Summary
Multipart uploads currently build the entire request body in memory before sending it. That means a large file upload can turn into a large memory spike, even though the file-handling path uses reader-backed values that look like they should stream.
I checked this on
mainat9eb2084.What I found
bytes.Bufferbefore the request option is created:https://github.com/openai/openai-cli/blob/9eb2084/pkg/cmd/flagoptions.go#L494-L510
fileUploadvalues:https://github.com/openai/openai-cli/blob/9eb2084/pkg/cmd/flagoptions.go#L154-L166
https://github.com/openai/openai-cli/blob/9eb2084/pkg/cmd/flagoptions.go#L276-L284
io.Copy, so the bytes still end up in the buffer above before the request is sent:https://github.com/openai/openai-cli/blob/9eb2084/internal/apiform/encoder.go#L169-L201
openFileUploadopens an*os.Fileand returns afileUpload, but the production multipart encoding path does not close that owned file after copying it:https://github.com/openai/openai-cli/blob/9eb2084/pkg/cmd/flagoptions.go#L550-L581
Why this matters
For endpoints that accept large files, the CLI can end up holding the full encoded multipart request in memory. That can make uploads slower than necessary, increase RSS significantly, or fail in constrained environments. There is also a small file descriptor leak risk because the upload file is opened for encoding but not closed by the normal request path.
Possible fix
A good direction would be to stream multipart encoding with
io.Pipe, so file contents are copied into the HTTP request as the client reads the body. The writer side should close owned upload files after each file reader is copied, while preserving the existing stdin behavior for@-/-inputs.One wrinkle: the current
bytes.Bufferbody is replayable by the Go SDK for retries. A plain streaming pipe body would not be replayable, so the fix should make that behavior explicit. A few options:The second option is probably the most complete long-term fix. The first option may be enough for a smaller CLI-side change if the retry tradeoff is acceptable.