-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrealtime.js
More file actions
123 lines (118 loc) · 3.1 KB
/
Copy pathrealtime.js
File metadata and controls
123 lines (118 loc) · 3.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import {
createTimeout,
jsonResponse,
readLimitedBytes,
} from "./http.js";
import {
OPENAI_KEY_HEADER,
openAiError,
validOpenAiKey,
} from "./openai-auth.js";
export { OPENAI_KEY_HEADER } from "./openai-auth.js";
export const OPENAI_REALTIME_MODEL = "gpt-realtime";
export const OPENAI_TRANSCRIPTION_MODEL = "gpt-live-transcribe";
const OPENAI_TOKEN_URL = "https://api.openai.com/v1/realtime/client_secrets";
const TOKEN_TIMEOUT_MS = 8_000;
const MAX_UPSTREAM_BYTES = 16_384;
export async function handleRealtimeTokenRequest(
request,
_env,
dependencies = {},
) {
const key = request.headers.get(OPENAI_KEY_HEADER)?.trim();
if (!key) {
return openAiError(
"OPENAI_KEY_REQUIRED",
"OpenAI key required",
401,
);
}
if (!validOpenAiKey(key)) {
return openAiError(
"OPENAI_KEY_INVALID",
"OpenAI key is invalid",
400,
);
}
let purpose = "assistant";
try {
const body = await request.clone().json();
if (body?.purpose === "transcription") purpose = "transcription";
} catch {
// Empty request bodies retain the Ask AI default.
}
const session = purpose === "transcription"
? { type: "transcription", audio: { input: { transcription: {
model: OPENAI_TRANSCRIPTION_MODEL,
} } } }
: { type: "realtime", model: OPENAI_REALTIME_MODEL };
const fetchImpl = dependencies.fetchImpl ?? fetch;
const timeout = createTimeout(TOKEN_TIMEOUT_MS);
let upstream;
try {
upstream = await fetchImpl(OPENAI_TOKEN_URL, {
method: "POST",
headers: {
Authorization: `Bearer ${key}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
session,
}),
signal: timeout.signal,
});
} catch (error) {
return openAiError(
error?.name === "AbortError"
? "OPENAI_TOKEN_TIMEOUT"
: "OPENAI_TOKEN_UNAVAILABLE",
error?.name === "AbortError"
? "OpenAI Realtime session timed out"
: "OpenAI Realtime session is unavailable",
error?.name === "AbortError" ? 504 : 502,
);
} finally {
timeout.dispose();
}
if (!upstream.ok) {
await upstream.body?.cancel().catch(() => undefined);
return openAiError(
"OPENAI_TOKEN_REJECTED",
"OpenAI rejected the Realtime session",
502,
);
}
let data;
try {
const bytes = await readLimitedBytes(upstream, MAX_UPSTREAM_BYTES);
data = JSON.parse(new TextDecoder().decode(bytes));
} catch {
return openAiError(
"OPENAI_TOKEN_INVALID",
"OpenAI returned an invalid Realtime session",
502,
);
}
if (
typeof data?.value !== "string"
|| data.value.length < 10
|| data.value.length > 4_096
|| !Number.isFinite(data.expires_at)
) {
return openAiError(
"OPENAI_TOKEN_INVALID",
"OpenAI returned an invalid Realtime session",
502,
);
}
return jsonResponse(
{
value: data.value,
expiresAt: data.expires_at,
model: purpose === "transcription"
? OPENAI_TRANSCRIPTION_MODEL
: OPENAI_REALTIME_MODEL,
},
{ headers: { "cache-control": "no-store" } },
);
}