Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ jobs:
runs-on: ${{ github.repository == 'stainless-sdks/openai-go' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: |-
github.repository == 'stainless-sdks/openai-go' &&
(github.event_name == 'push' || github.event.pull_request.head.repo.fork) &&
(github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
(github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

Expand Down
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "3.30.0"
".": "3.31.0"
}
6 changes: 3 additions & 3 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 139
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai%2Fopenai-00994178cc8e20d71754b00c54b0e4f5b4128e1c1cce765e9b7d696bd8c80d33.yml
openapi_spec_hash: 81f404053b663f987209b4fb2d08a230
config_hash: 5635033cdc8c930255f8b529a78de722
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai%2Fopenai-a6eca1bd01e0c434af356fe5275c206057216a4e626d1051d294c27016cd6d05.yml
openapi_spec_hash: 68abda9122013a9ae3f084cfdbe8e8c1
config_hash: 4975e16a94e8f9901428022044131888
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
# Changelog

## 3.31.0 (2026-04-08)

Full Changelog: [v3.30.0...v3.31.0](https://github.com/openai/openai-go/compare/v3.30.0...v3.31.0)

### Features

* **api:** add phase field to conversation messages ([563d747](https://github.com/openai/openai-go/commit/563d747fff0370d16e5cffff5a11e8e02e17bb8b))
* **api:** add web_search_call.results to ResponseIncludable ([6ef5b20](https://github.com/openai/openai-go/commit/6ef5b20a234ebdc85bd580539989a3933f06b5df))
* **client:** add support for short-lived tokens ([#799](https://github.com/openai/openai-go/issues/799)) ([038871e](https://github.com/openai/openai-go/commit/038871ef135200279e45bd8920b0d142c58e51c0))
* **internal:** support comma format in multipart form encoding ([905b473](https://github.com/openai/openai-go/commit/905b473d7bfb14ffd1db7c7b48efc1fdb84d1750))


### Bug Fixes

* fix issue with unmarshaling in some cases ([a017bc6](https://github.com/openai/openai-go/commit/a017bc6b810fcb01558736cd742f180dce342e49))
* prevent duplicate ? in query params ([cbb2c75](https://github.com/openai/openai-go/commit/cbb2c754223558d6fd19f34642a0aa89adba42e7))
* **types:** remove web_search_call.results from ResponseIncludable ([180597f](https://github.com/openai/openai-go/commit/180597fddba09d631a2fdc786116b3ad4f2e5418))


### Chores

* **ci:** support opting out of skipping builds on metadata-only commits ([12e3a5a](https://github.com/openai/openai-go/commit/12e3a5afdbacd2fad57640ba5486226898d6df71))
* remove unnecessary error check for url parsing ([cfe9c41](https://github.com/openai/openai-go/commit/cfe9c411d11b6f097d6e0563767bf231abb9975a))
* **tests:** bump steady to v0.20.1 ([8e4ef11](https://github.com/openai/openai-go/commit/8e4ef117bdec78698d72a00b0a14d12cf0482561))
* **tests:** bump steady to v0.20.2 ([ad31f81](https://github.com/openai/openai-go/commit/ad31f81e22e4f2cc931e11625755f92978a18be9))
* update docs for api:"required" ([2d16ebc](https://github.com/openai/openai-go/commit/2d16ebc57c3eca70c5bcb421ee8592549722e7f4))


### Documentation

* **api:** add multi-file ingestion guidance to vectorstorefile/filebatch params ([dbba33f](https://github.com/openai/openai-go/commit/dbba33f751ffa7e7a36678c4305a3e7630a4494f))

## 3.30.0 (2026-03-25)

Full Changelog: [v3.29.0...v3.30.0](https://github.com/openai/openai-go/compare/v3.29.0...v3.30.0)
Expand Down
102 changes: 100 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Or to pin the version:
<!-- x-release-please-start-version -->

```sh
go get -u 'github.com/openai/openai-go/v3@v3.30.0'
go get -u 'github.com/openai/openai-go/v3@v3.31.0'
```

<!-- x-release-please-end -->
Expand Down Expand Up @@ -367,7 +367,7 @@ func main() {
The openai library uses the [`omitzero`](https://tip.golang.org/doc/go1.24#encodingjsonpkgencodingjson)
semantics from the Go 1.24+ `encoding/json` release for request fields.

Required primitive fields (`int64`, `string`, etc.) feature the tag <code>\`json:"...,required"\`</code>. These
Required primitive fields (`int64`, `string`, etc.) feature the tag <code>\`api:"required"\`</code>. These
fields are always serialized, even their zero values.

Optional primitive types are wrapped in a `param.Opt[T]`. These fields can be set with the provided constructors, `openai.String(string)`, `openai.Int(int64)`, etc.
Expand Down Expand Up @@ -953,6 +953,104 @@ You may also replace the default `http.Client` with
accepted (this overwrites any previous client) and receives requests after any
middleware has been applied.

## Workload Identity Authentication

For cloud workloads (Kubernetes, Azure, Google Cloud Platform), you can use workload identity authentication instead of API keys. This provides short-lived tokens that are automatically refreshed.

### Kubernetes

```go
import (
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/auth"
"github.com/openai/openai-go/v3/option"
)

client := openai.NewClient(
option.WithWorkloadIdentity(auth.WorkloadIdentity{
ClientID: "your-client-id",
IdentityProviderID: "idp-123",
ServiceAccountID: "sa-456",
Provider: auth.K8sServiceAccountTokenProvider(""),
}),
)
```

### Azure Managed Identity

```go
client := openai.NewClient(
option.WithWorkloadIdentity(auth.WorkloadIdentity{
ClientID: "your-client-id",
IdentityProviderID: "idp-123",
ServiceAccountID: "sa-456",
Provider: auth.AzureManagedIdentityTokenProvider(nil),
}),
)
```

### Google Cloud Compute Engine

```go
client := openai.NewClient(
option.WithWorkloadIdentity(auth.WorkloadIdentity{
ClientID: "your-client-id",
IdentityProviderID: "idp-123",
ServiceAccountID: "sa-456",
Provider: auth.GCPIDTokenProvider(nil),
}),
)
```

### Custom Subject Token Provider

You can implement your own subject token provider:

```go
import (
"context"

"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/auth"
"github.com/openai/openai-go/v3/option"
)

type customTokenProvider struct{}

func (p *customTokenProvider) TokenType() auth.SubjectTokenType {
return auth.SubjectTokenTypeJWT
}

func (p *customTokenProvider) GetToken(ctx context.Context, httpClient auth.HTTPDoer) (string, error) {
return "your-token", nil
}

client := openai.NewClient(
option.WithWorkloadIdentity(auth.WorkloadIdentity{
ClientID: "your-client-id",
IdentityProviderID: "idp-123",
ServiceAccountID: "sa-456",
Provider: &customTokenProvider{},
}),
)
```

### Customizing Refresh Buffer

By default, tokens are refreshed 20 minutes (1200 seconds) before expiry. You can customize this:

```go
client := openai.NewClient(
option.WithWorkloadIdentity(auth.WorkloadIdentity{
ClientID: "your-client-id",
IdentityProviderID: "idp-123",
ServiceAccountID: "sa-456",
Provider: auth.K8sServiceAccountTokenProvider(""),
RefreshBufferSeconds: 600,
}),
)
```

## Microsoft Azure OpenAI

To use this library with [Azure OpenAI]https://learn.microsoft.com/azure/ai-services/openai/overview),
Expand Down
9 changes: 9 additions & 0 deletions aliases.go
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,15 @@ type FunctionParameters = shared.FunctionParameters
// This is an alias to an internal type.
type Metadata = shared.Metadata

// This is an alias to an internal type.
type OAuthErrorCode = shared.OAuthErrorCode

// Equals "invalid_grant"
const OAuthErrorCodeInvalidGrant = shared.OAuthErrorCodeInvalidGrant

// Equals "invalid_subject_token"
const OAuthErrorCodeInvalidSubjectToken = shared.OAuthErrorCodeInvalidSubjectToken

// **gpt-5 and o-series models only**
//
// Configuration options for
Expand Down
1 change: 1 addition & 0 deletions api.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- <a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared">shared</a>.<a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared#FunctionDefinitionParam">FunctionDefinitionParam</a>
- <a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared">shared</a>.<a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared#FunctionParameters">FunctionParameters</a>
- <a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared">shared</a>.<a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared#Metadata">Metadata</a>
- <a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared">shared</a>.<a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared#OAuthErrorCode">OAuthErrorCode</a>
- <a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared">shared</a>.<a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared#ReasoningParam">ReasoningParam</a>
- <a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared">shared</a>.<a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared#ReasoningEffort">ReasoningEffort</a>
- <a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared">shared</a>.<a href="https://pkg.go.dev/github.com/openai/openai-go/v3/shared#ResponseFormatJSONObjectParam">ResponseFormatJSONObjectParam</a>
Expand Down
37 changes: 37 additions & 0 deletions auth/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package auth

import (
"fmt"

"github.com/openai/openai-go/v3/shared"
)

// SubjectTokenProviderError is raised when failing to get the subject token from the cloud environment.
type SubjectTokenProviderError struct {
Provider string
Message string
Cause error
}

func (e *SubjectTokenProviderError) Error() string {
if e.Cause != nil {
return fmt.Sprintf("%s provider error: %s: %v", e.Provider, e.Message, e.Cause)
}
return fmt.Sprintf("%s provider error: %s", e.Provider, e.Message)
}

func (e *SubjectTokenProviderError) Unwrap() error {
return e.Cause
}

// OAuthError is raised when there is an error in the RFC-defined OAuth flow, such as failing to exchange the token.
// See https://datatracker.ietf.org/doc/html/rfc8693 for the OAuth 2.0 Token Exchange specification.
type OAuthError struct {
StatusCode int
ErrorCode shared.OAuthErrorCode
ErrorDescription string
}

func (e *OAuthError) Error() string {
return fmt.Sprintf("OAuth error (status %d): %s - %s", e.StatusCode, e.ErrorCode, e.ErrorDescription)
}
50 changes: 50 additions & 0 deletions auth/middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package auth

import (
"net/http"
)

func WorkloadIdentityMiddleware(
wia *WorkloadIdentityAuth,
httpClient HTTPDoer,
req *http.Request,
next func(*http.Request) (*http.Response, error),
) (*http.Response, error) {
token, err := wia.GetToken(req.Context(), httpClient)
if err != nil {
return nil, err
}

req.Header.Set("Authorization", "Bearer "+token)

resp, err := next(req)
if err != nil || resp == nil || resp.StatusCode != http.StatusUnauthorized {
return resp, err
}

wia.invalidateToken()

if req.Body != nil && req.GetBody == nil {
return resp, nil
}

retryReq := req.Clone(req.Context())

token, err = wia.GetToken(req.Context(), httpClient)
if err != nil {
resp.Body.Close()
return nil, err
}
retryReq.Header.Set("Authorization", "Bearer "+token)

if req.GetBody != nil {
retryReq.Body, err = req.GetBody()
if err != nil {
resp.Body.Close()
return nil, err
}
}

resp.Body.Close()
return next(retryReq)
}
Loading
Loading