Skip to content

Commit d84f4e5

Browse files
feat(api): add support for realtime calls
1 parent f3dd556 commit d84f4e5

File tree

6 files changed

+344
-4
lines changed

6 files changed

+344
-4
lines changed

.stats.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
configured_endpoints: 106
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai%2Fopenai-e205b1f2da6a1f2caa229efa9ede63f2d3d2fedeeb2dd6ed3d880bafdcb0ab88.yml
3-
openapi_spec_hash: c8aee2469a749f6a838b40c57e4b7b06
4-
config_hash: 45dcba51451ba532959c020a0ddbf23c
1+
configured_endpoints: 110
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai%2Fopenai-fadefdc7c7e30df47c09df323669b242ff90ee08e51f304175ace5274e0aab49.yml
3+
openapi_spec_hash: 6d20f639d9ff8a097a34962da6218231
4+
config_hash: 902654e60f5d659f2bfcfd903e17c46d

api.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,15 @@ Methods:
841841

842842
- <code title="post /realtime/client_secrets">client.Realtime.ClientSecrets.<a href="https://pkg.go.dev/github.com/openai/openai-go/v3/realtime#ClientSecretService.New">New</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/openai/openai-go/v3/realtime">realtime</a>.<a href="https://pkg.go.dev/github.com/openai/openai-go/v3/realtime#ClientSecretNewParams">ClientSecretNewParams</a>) (<a href="https://pkg.go.dev/github.com/openai/openai-go/v3/realtime">realtime</a>.<a href="https://pkg.go.dev/github.com/openai/openai-go/v3/realtime#ClientSecretNewResponse">ClientSecretNewResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
843843

844+
## Calls
845+
846+
Methods:
847+
848+
- <code title="post /realtime/calls/{call_id}/accept">client.Realtime.Calls.<a href="https://pkg.go.dev/github.com/openai/openai-go/v3/realtime#CallService.Accept">Accept</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, callID <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/openai/openai-go/v3/realtime">realtime</a>.<a href="https://pkg.go.dev/github.com/openai/openai-go/v3/realtime#CallAcceptParams">CallAcceptParams</a>) <a href="https://pkg.go.dev/builtin#error">error</a></code>
849+
- <code title="post /realtime/calls/{call_id}/hangup">client.Realtime.Calls.<a href="https://pkg.go.dev/github.com/openai/openai-go/v3/realtime#CallService.Hangup">Hangup</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, callID <a href="https://pkg.go.dev/builtin#string">string</a>) <a href="https://pkg.go.dev/builtin#error">error</a></code>
850+
- <code title="post /realtime/calls/{call_id}/refer">client.Realtime.Calls.<a href="https://pkg.go.dev/github.com/openai/openai-go/v3/realtime#CallService.Refer">Refer</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, callID <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/openai/openai-go/v3/realtime">realtime</a>.<a href="https://pkg.go.dev/github.com/openai/openai-go/v3/realtime#CallReferParams">CallReferParams</a>) <a href="https://pkg.go.dev/builtin#error">error</a></code>
851+
- <code title="post /realtime/calls/{call_id}/reject">client.Realtime.Calls.<a href="https://pkg.go.dev/github.com/openai/openai-go/v3/realtime#CallService.Reject">Reject</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, callID <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/openai/openai-go/v3/realtime">realtime</a>.<a href="https://pkg.go.dev/github.com/openai/openai-go/v3/realtime#CallRejectParams">CallRejectParams</a>) <a href="https://pkg.go.dev/builtin#error">error</a></code>
852+
844853
# Conversations
845854

846855
Response Types:

realtime/call.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2+
3+
package realtime
4+
5+
import (
6+
"context"
7+
"encoding/json"
8+
"errors"
9+
"fmt"
10+
"net/http"
11+
"slices"
12+
13+
"github.com/openai/openai-go/v3/internal/apijson"
14+
shimjson "github.com/openai/openai-go/v3/internal/encoding/json"
15+
"github.com/openai/openai-go/v3/internal/requestconfig"
16+
"github.com/openai/openai-go/v3/option"
17+
"github.com/openai/openai-go/v3/packages/param"
18+
)
19+
20+
// CallService contains methods and other services that help with interacting with
21+
// the openai API.
22+
//
23+
// Note, unlike clients, this service does not read variables from the environment
24+
// automatically. You should not instantiate this service directly, and instead use
25+
// the [NewCallService] method instead.
26+
type CallService struct {
27+
Options []option.RequestOption
28+
}
29+
30+
// NewCallService generates a new service that applies the given options to each
31+
// request. These options are applied after the parent client's options (if there
32+
// is one), and before any request-specific options.
33+
func NewCallService(opts ...option.RequestOption) (r CallService) {
34+
r = CallService{}
35+
r.Options = opts
36+
return
37+
}
38+
39+
// Accept an incoming SIP call and configure the realtime session that will handle
40+
// it.
41+
func (r *CallService) Accept(ctx context.Context, callID string, body CallAcceptParams, opts ...option.RequestOption) (err error) {
42+
opts = slices.Concat(r.Options, opts)
43+
opts = append([]option.RequestOption{option.WithHeader("Accept", "")}, opts...)
44+
if callID == "" {
45+
err = errors.New("missing required call_id parameter")
46+
return
47+
}
48+
path := fmt.Sprintf("realtime/calls/%s/accept", callID)
49+
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, nil, opts...)
50+
return
51+
}
52+
53+
// End an active Realtime API call, whether it was initiated over SIP or WebRTC.
54+
func (r *CallService) Hangup(ctx context.Context, callID string, opts ...option.RequestOption) (err error) {
55+
opts = slices.Concat(r.Options, opts)
56+
opts = append([]option.RequestOption{option.WithHeader("Accept", "")}, opts...)
57+
if callID == "" {
58+
err = errors.New("missing required call_id parameter")
59+
return
60+
}
61+
path := fmt.Sprintf("realtime/calls/%s/hangup", callID)
62+
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, nil, opts...)
63+
return
64+
}
65+
66+
// Transfer an active SIP call to a new destination using the SIP REFER verb.
67+
func (r *CallService) Refer(ctx context.Context, callID string, body CallReferParams, opts ...option.RequestOption) (err error) {
68+
opts = slices.Concat(r.Options, opts)
69+
opts = append([]option.RequestOption{option.WithHeader("Accept", "")}, opts...)
70+
if callID == "" {
71+
err = errors.New("missing required call_id parameter")
72+
return
73+
}
74+
path := fmt.Sprintf("realtime/calls/%s/refer", callID)
75+
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, nil, opts...)
76+
return
77+
}
78+
79+
// Decline an incoming SIP call by returning a SIP status code to the caller.
80+
func (r *CallService) Reject(ctx context.Context, callID string, body CallRejectParams, opts ...option.RequestOption) (err error) {
81+
opts = slices.Concat(r.Options, opts)
82+
opts = append([]option.RequestOption{option.WithHeader("Accept", "")}, opts...)
83+
if callID == "" {
84+
err = errors.New("missing required call_id parameter")
85+
return
86+
}
87+
path := fmt.Sprintf("realtime/calls/%s/reject", callID)
88+
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, nil, opts...)
89+
return
90+
}
91+
92+
type CallAcceptParams struct {
93+
// Realtime session object configuration.
94+
RealtimeSessionCreateRequest RealtimeSessionCreateRequestParam
95+
paramObj
96+
}
97+
98+
func (r CallAcceptParams) MarshalJSON() (data []byte, err error) {
99+
return shimjson.Marshal(r.RealtimeSessionCreateRequest)
100+
}
101+
func (r *CallAcceptParams) UnmarshalJSON(data []byte) error {
102+
return json.Unmarshal(data, &r.RealtimeSessionCreateRequest)
103+
}
104+
105+
type CallReferParams struct {
106+
// URI that should appear in the SIP Refer-To header. Supports values like
107+
// `tel:+14155550123` or `sip:[email protected]`.
108+
TargetUri string `json:"target_uri,required"`
109+
paramObj
110+
}
111+
112+
func (r CallReferParams) MarshalJSON() (data []byte, err error) {
113+
type shadow CallReferParams
114+
return param.MarshalObject(r, (*shadow)(&r))
115+
}
116+
func (r *CallReferParams) UnmarshalJSON(data []byte) error {
117+
return apijson.UnmarshalRoot(data, r)
118+
}
119+
120+
type CallRejectParams struct {
121+
// SIP response code to send back to the caller. Defaults to `603` (Decline) when
122+
// omitted.
123+
StatusCode param.Opt[int64] `json:"status_code,omitzero"`
124+
paramObj
125+
}
126+
127+
func (r CallRejectParams) MarshalJSON() (data []byte, err error) {
128+
type shadow CallRejectParams
129+
return param.MarshalObject(r, (*shadow)(&r))
130+
}
131+
func (r *CallRejectParams) UnmarshalJSON(data []byte) error {
132+
return apijson.UnmarshalRoot(data, r)
133+
}

realtime/call_test.go

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2+
3+
package realtime_test
4+
5+
import (
6+
"context"
7+
"errors"
8+
"os"
9+
"testing"
10+
11+
"github.com/openai/openai-go/v3"
12+
"github.com/openai/openai-go/v3/internal/testutil"
13+
"github.com/openai/openai-go/v3/option"
14+
"github.com/openai/openai-go/v3/realtime"
15+
"github.com/openai/openai-go/v3/responses"
16+
"github.com/openai/openai-go/v3/shared/constant"
17+
)
18+
19+
func TestCallAcceptWithOptionalParams(t *testing.T) {
20+
baseURL := "http://localhost:4010"
21+
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
22+
baseURL = envURL
23+
}
24+
if !testutil.CheckTestServer(t, baseURL) {
25+
return
26+
}
27+
client := openai.NewClient(
28+
option.WithBaseURL(baseURL),
29+
option.WithAPIKey("My API Key"),
30+
)
31+
err := client.Realtime.Calls.Accept(
32+
context.TODO(),
33+
"call_id",
34+
realtime.CallAcceptParams{
35+
RealtimeSessionCreateRequest: realtime.RealtimeSessionCreateRequestParam{
36+
Audio: realtime.RealtimeAudioConfigParam{
37+
Input: realtime.RealtimeAudioConfigInputParam{
38+
Format: realtime.RealtimeAudioFormatsUnionParam{
39+
OfAudioPCM: &realtime.RealtimeAudioFormatsAudioPCMParam{
40+
Rate: 24000,
41+
Type: "audio/pcm",
42+
},
43+
},
44+
NoiseReduction: realtime.RealtimeAudioConfigInputNoiseReductionParam{
45+
Type: realtime.NoiseReductionTypeNearField,
46+
},
47+
Transcription: realtime.AudioTranscriptionParam{
48+
Language: openai.String("language"),
49+
Model: realtime.AudioTranscriptionModelWhisper1,
50+
Prompt: openai.String("prompt"),
51+
},
52+
TurnDetection: realtime.RealtimeAudioInputTurnDetectionUnionParam{
53+
OfServerVad: &realtime.RealtimeAudioInputTurnDetectionServerVadParam{
54+
CreateResponse: openai.Bool(true),
55+
IdleTimeoutMs: openai.Int(5000),
56+
InterruptResponse: openai.Bool(true),
57+
PrefixPaddingMs: openai.Int(0),
58+
SilenceDurationMs: openai.Int(0),
59+
Threshold: openai.Float(0),
60+
},
61+
},
62+
},
63+
Output: realtime.RealtimeAudioConfigOutputParam{
64+
Format: realtime.RealtimeAudioFormatsUnionParam{
65+
OfAudioPCM: &realtime.RealtimeAudioFormatsAudioPCMParam{
66+
Rate: 24000,
67+
Type: "audio/pcm",
68+
},
69+
},
70+
Speed: openai.Float(0.25),
71+
Voice: realtime.RealtimeAudioConfigOutputVoiceAlloy,
72+
},
73+
},
74+
Include: []string{"item.input_audio_transcription.logprobs"},
75+
Instructions: openai.String("instructions"),
76+
MaxOutputTokens: realtime.RealtimeSessionCreateRequestMaxOutputTokensUnionParam{
77+
OfInt: openai.Int(0),
78+
},
79+
Model: realtime.RealtimeSessionCreateRequestModelGPTRealtime,
80+
OutputModalities: []string{"text"},
81+
Prompt: responses.ResponsePromptParam{
82+
ID: "id",
83+
Variables: map[string]responses.ResponsePromptVariableUnionParam{
84+
"foo": {
85+
OfString: openai.String("string"),
86+
},
87+
},
88+
Version: openai.String("version"),
89+
},
90+
ToolChoice: realtime.RealtimeToolChoiceConfigUnionParam{
91+
OfToolChoiceMode: openai.Opt(responses.ToolChoiceOptionsNone),
92+
},
93+
Tools: realtime.RealtimeToolsConfigParam{realtime.RealtimeToolsConfigUnionParam{
94+
OfFunction: &realtime.RealtimeFunctionToolParam{
95+
Description: openai.String("description"),
96+
Name: openai.String("name"),
97+
Parameters: map[string]interface{}{},
98+
Type: realtime.RealtimeFunctionToolTypeFunction,
99+
},
100+
}},
101+
Tracing: realtime.RealtimeTracingConfigUnionParam{
102+
OfAuto: constant.ValueOf[constant.Auto](),
103+
},
104+
Truncation: realtime.RealtimeTruncationUnionParam{
105+
OfRealtimeTruncationStrategy: openai.String("auto"),
106+
},
107+
},
108+
},
109+
)
110+
if err != nil {
111+
var apierr *openai.Error
112+
if errors.As(err, &apierr) {
113+
t.Log(string(apierr.DumpRequest(true)))
114+
}
115+
t.Fatalf("err should be nil: %s", err.Error())
116+
}
117+
}
118+
119+
func TestCallHangup(t *testing.T) {
120+
baseURL := "http://localhost:4010"
121+
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
122+
baseURL = envURL
123+
}
124+
if !testutil.CheckTestServer(t, baseURL) {
125+
return
126+
}
127+
client := openai.NewClient(
128+
option.WithBaseURL(baseURL),
129+
option.WithAPIKey("My API Key"),
130+
)
131+
err := client.Realtime.Calls.Hangup(context.TODO(), "call_id")
132+
if err != nil {
133+
var apierr *openai.Error
134+
if errors.As(err, &apierr) {
135+
t.Log(string(apierr.DumpRequest(true)))
136+
}
137+
t.Fatalf("err should be nil: %s", err.Error())
138+
}
139+
}
140+
141+
func TestCallRefer(t *testing.T) {
142+
baseURL := "http://localhost:4010"
143+
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
144+
baseURL = envURL
145+
}
146+
if !testutil.CheckTestServer(t, baseURL) {
147+
return
148+
}
149+
client := openai.NewClient(
150+
option.WithBaseURL(baseURL),
151+
option.WithAPIKey("My API Key"),
152+
)
153+
err := client.Realtime.Calls.Refer(
154+
context.TODO(),
155+
"call_id",
156+
realtime.CallReferParams{
157+
TargetUri: "tel:+14155550123",
158+
},
159+
)
160+
if err != nil {
161+
var apierr *openai.Error
162+
if errors.As(err, &apierr) {
163+
t.Log(string(apierr.DumpRequest(true)))
164+
}
165+
t.Fatalf("err should be nil: %s", err.Error())
166+
}
167+
}
168+
169+
func TestCallRejectWithOptionalParams(t *testing.T) {
170+
baseURL := "http://localhost:4010"
171+
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
172+
baseURL = envURL
173+
}
174+
if !testutil.CheckTestServer(t, baseURL) {
175+
return
176+
}
177+
client := openai.NewClient(
178+
option.WithBaseURL(baseURL),
179+
option.WithAPIKey("My API Key"),
180+
)
181+
err := client.Realtime.Calls.Reject(
182+
context.TODO(),
183+
"call_id",
184+
realtime.CallRejectParams{
185+
StatusCode: openai.Int(486),
186+
},
187+
)
188+
if err != nil {
189+
var apierr *openai.Error
190+
if errors.As(err, &apierr) {
191+
t.Log(string(apierr.DumpRequest(true)))
192+
}
193+
t.Fatalf("err should be nil: %s", err.Error())
194+
}
195+
}

realtime/realtime.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
type RealtimeService struct {
2323
Options []option.RequestOption
2424
ClientSecrets ClientSecretService
25+
Calls CallService
2526
}
2627

2728
// NewRealtimeService generates a new service that applies the given options to
@@ -31,6 +32,7 @@ func NewRealtimeService(opts ...option.RequestOption) (r RealtimeService) {
3132
r = RealtimeService{}
3233
r.Options = opts
3334
r.ClientSecrets = NewClientSecretService(opts...)
35+
r.Calls = NewCallService(opts...)
3436
return
3537
}
3638

scripts/detect-breaking-changes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ TEST_PATHS=(
3838
responses/response_test.go
3939
responses/inputitem_test.go
4040
realtime/clientsecret_test.go
41+
realtime/call_test.go
4142
conversations/conversation_test.go
4243
conversations/item_test.go
4344
container_test.go

0 commit comments

Comments
 (0)