@builder.io/ai-utils 0.81.3 → 0.82.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/codegen/investigation-context.d.ts +1 -1
- package/src/codegen.d.ts +9 -0
- package/src/completion.d.ts +2 -1
- package/src/editor-ai.d.ts +53 -0
- package/src/editor-ai.js +69 -0
- package/src/editor-ai.test.d.ts +1 -0
- package/src/editor-ai.test.js +37 -0
- package/src/index.d.ts +2 -0
- package/src/index.js +2 -0
- package/src/messages.d.ts +15 -0
- package/src/projects.d.ts +0 -9
- package/src/projects.js +0 -7
- package/src/proxy.d.ts +1 -1
- package/src/realtime.d.ts +232 -0
- package/src/realtime.js +158 -0
- package/src/realtime.spec.d.ts +1 -0
- package/src/realtime.spec.js +122 -0
package/package.json
CHANGED
|
@@ -18,5 +18,5 @@ export interface BuildInvestigationSystemPromptInput {
|
|
|
18
18
|
event: InvestigationEvent;
|
|
19
19
|
completionJson: InvestigationCompletionJSON | undefined;
|
|
20
20
|
}
|
|
21
|
-
export declare function buildInvestigationSystemPrompt({ event, completionJson }: BuildInvestigationSystemPromptInput): string;
|
|
21
|
+
export declare function buildInvestigationSystemPrompt({ event, completionJson, }: BuildInvestigationSystemPromptInput): string;
|
|
22
22
|
export declare const SUGGESTED_QUESTIONS: readonly string[];
|
package/src/codegen.d.ts
CHANGED
|
@@ -2890,6 +2890,8 @@ export declare const CodeGenInputOptionsSchema: z.ZodObject<{
|
|
|
2890
2890
|
systemReminderPrompt: z.ZodOptional<z.ZodString>;
|
|
2891
2891
|
}, z.core.$loose>;
|
|
2892
2892
|
export type CodeGenInputOptions = z.infer<typeof CodeGenInputOptionsSchema> & {
|
|
2893
|
+
/** @internal Derived from BuilderEditorState.components for editor AI. */
|
|
2894
|
+
_allowedComponentNames?: string[];
|
|
2893
2895
|
/**
|
|
2894
2896
|
* Optional factory function to wrap the MCP client with custom behavior
|
|
2895
2897
|
* Used by Editor AI to add permission checks for model operations
|
|
@@ -3950,6 +3952,13 @@ export interface MCPAuthRequiredServer {
|
|
|
3950
3952
|
authorizationUrl: string;
|
|
3951
3953
|
scopes: string[];
|
|
3952
3954
|
isReauth: boolean;
|
|
3955
|
+
/**
|
|
3956
|
+
* Server opted in to the LLM-driven `__authenticate` flow via its Firestore
|
|
3957
|
+
* `useAuthenticateTool` field. Servers opted in this way (or, org-wide, via
|
|
3958
|
+
* the `codegen-tool-mcp-authenticate` LD flag) get a synthetic auth tool
|
|
3959
|
+
* instead of a blocking `mcp-auth-required` event.
|
|
3960
|
+
*/
|
|
3961
|
+
useAuthenticateTool?: boolean;
|
|
3953
3962
|
}
|
|
3954
3963
|
export interface GenerateCodeEventMCPAuthRequired {
|
|
3955
3964
|
type: "mcp-auth-required";
|
package/src/completion.d.ts
CHANGED
|
@@ -99,7 +99,8 @@ export interface BuilderEditorState {
|
|
|
99
99
|
*/
|
|
100
100
|
content?: BuilderContent;
|
|
101
101
|
/**
|
|
102
|
-
* Builder
|
|
102
|
+
* Components available in the Builder editor, including built-ins.
|
|
103
|
+
* When present, editor AI restricts generated content to this list.
|
|
103
104
|
*/
|
|
104
105
|
components?: Component[];
|
|
105
106
|
/**
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const EDITOR_AI_READ_DEFAULT_LIMIT = 250;
|
|
3
|
+
export declare const EDITOR_AI_READ_MAX_LIMIT = 1500;
|
|
4
|
+
export declare const EditorAiReadRequestSchema: z.ZodObject<{
|
|
5
|
+
contentId: z.ZodString;
|
|
6
|
+
offset: z.ZodOptional<z.ZodNumber>;
|
|
7
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
8
|
+
activeLocale: z.ZodOptional<z.ZodString>;
|
|
9
|
+
}, z.core.$strip>;
|
|
10
|
+
export type EditorAiReadRequest = z.infer<typeof EditorAiReadRequestSchema>;
|
|
11
|
+
export declare const EditorAiReadResponseSchema: z.ZodObject<{
|
|
12
|
+
contentId: z.ZodString;
|
|
13
|
+
totalLines: z.ZodNumber;
|
|
14
|
+
offset: z.ZodNumber;
|
|
15
|
+
limit: z.ZodNumber;
|
|
16
|
+
activeLocale: z.ZodString;
|
|
17
|
+
content: z.ZodString;
|
|
18
|
+
}, z.core.$strip>;
|
|
19
|
+
export type EditorAiReadResponse = z.infer<typeof EditorAiReadResponseSchema>;
|
|
20
|
+
export declare const EditorAiEditRequestSchema: z.ZodObject<{
|
|
21
|
+
contentId: z.ZodString;
|
|
22
|
+
old_str: z.ZodString;
|
|
23
|
+
new_str: z.ZodString;
|
|
24
|
+
activeLocale: z.ZodOptional<z.ZodString>;
|
|
25
|
+
}, z.core.$strip>;
|
|
26
|
+
export type EditorAiEditRequest = z.infer<typeof EditorAiEditRequestSchema>;
|
|
27
|
+
export declare const EditorAiEditResponseSchema: z.ZodObject<{
|
|
28
|
+
contentId: z.ZodString;
|
|
29
|
+
patch: z.ZodString;
|
|
30
|
+
modifiedBuilderContent: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
31
|
+
}, z.core.$strip>;
|
|
32
|
+
export type EditorAiEditResponse = z.infer<typeof EditorAiEditResponseSchema>;
|
|
33
|
+
export declare const EditorAiWriteRequestSchema: z.ZodObject<{
|
|
34
|
+
contentId: z.ZodString;
|
|
35
|
+
content: z.ZodString;
|
|
36
|
+
activeLocale: z.ZodOptional<z.ZodString>;
|
|
37
|
+
}, z.core.$strip>;
|
|
38
|
+
export type EditorAiWriteRequest = z.infer<typeof EditorAiWriteRequestSchema>;
|
|
39
|
+
export declare const EditorAiWriteResponseSchema: z.ZodObject<{
|
|
40
|
+
contentId: z.ZodString;
|
|
41
|
+
modifiedBuilderContent: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
42
|
+
}, z.core.$strip>;
|
|
43
|
+
export type EditorAiWriteResponse = z.infer<typeof EditorAiWriteResponseSchema>;
|
|
44
|
+
export type EditorAiErrorCode = "BAD_REQUEST" | "UNAUTHORIZED" | "FORBIDDEN" | "NOT_FOUND" | "NO_MATCH" | "VALIDATION_FAILED" | "INTERNAL";
|
|
45
|
+
export declare const EditorAiErrorBodySchema: z.ZodObject<{
|
|
46
|
+
error: z.ZodObject<{
|
|
47
|
+
code: z.ZodString;
|
|
48
|
+
message: z.ZodString;
|
|
49
|
+
hint: z.ZodOptional<z.ZodString>;
|
|
50
|
+
details: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
51
|
+
}, z.core.$strip>;
|
|
52
|
+
}, z.core.$strip>;
|
|
53
|
+
export type EditorAiErrorBody = z.infer<typeof EditorAiErrorBodySchema>;
|
package/src/editor-ai.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// Request/response contracts for the stateless /editor/read|edit|write
|
|
3
|
+
// endpoints. Shared so the Builder CMS MCP server reuses the same shapes.
|
|
4
|
+
export const EDITOR_AI_READ_DEFAULT_LIMIT = 250;
|
|
5
|
+
export const EDITOR_AI_READ_MAX_LIMIT = 1500;
|
|
6
|
+
export const EditorAiReadRequestSchema = z
|
|
7
|
+
.object({
|
|
8
|
+
contentId: z.string().min(1),
|
|
9
|
+
offset: z.number().int().min(0).optional(),
|
|
10
|
+
limit: z.number().int().min(1).max(EDITOR_AI_READ_MAX_LIMIT).optional(),
|
|
11
|
+
// Locale to operate on; defaults to "Default" when omitted.
|
|
12
|
+
activeLocale: z.string().min(1).optional(),
|
|
13
|
+
})
|
|
14
|
+
.meta({ title: "EditorAiReadRequest" });
|
|
15
|
+
export const EditorAiReadResponseSchema = z
|
|
16
|
+
.object({
|
|
17
|
+
contentId: z.string(),
|
|
18
|
+
totalLines: z.number().int(),
|
|
19
|
+
offset: z.number().int(),
|
|
20
|
+
limit: z.number().int(),
|
|
21
|
+
activeLocale: z.string(),
|
|
22
|
+
content: z.string(),
|
|
23
|
+
})
|
|
24
|
+
.meta({ title: "EditorAiReadResponse" });
|
|
25
|
+
export const EditorAiEditRequestSchema = z
|
|
26
|
+
.object({
|
|
27
|
+
contentId: z.string().min(1),
|
|
28
|
+
old_str: z
|
|
29
|
+
.string()
|
|
30
|
+
.min(1)
|
|
31
|
+
.refine((s) => s.trim().length > 0, {
|
|
32
|
+
message: "old_str must not be only whitespace",
|
|
33
|
+
}),
|
|
34
|
+
new_str: z.string(),
|
|
35
|
+
// Locale to operate on; defaults to "Default" when omitted.
|
|
36
|
+
activeLocale: z.string().min(1).optional(),
|
|
37
|
+
})
|
|
38
|
+
.meta({ title: "EditorAiEditRequest" });
|
|
39
|
+
export const EditorAiEditResponseSchema = z
|
|
40
|
+
.object({
|
|
41
|
+
contentId: z.string(),
|
|
42
|
+
patch: z.string(),
|
|
43
|
+
modifiedBuilderContent: z.record(z.string(), z.unknown()),
|
|
44
|
+
})
|
|
45
|
+
.meta({ title: "EditorAiEditResponse" });
|
|
46
|
+
export const EditorAiWriteRequestSchema = z
|
|
47
|
+
.object({
|
|
48
|
+
contentId: z.string().min(1),
|
|
49
|
+
content: z.string().min(1),
|
|
50
|
+
// Locale to operate on; defaults to "Default" when omitted.
|
|
51
|
+
activeLocale: z.string().min(1).optional(),
|
|
52
|
+
})
|
|
53
|
+
.meta({ title: "EditorAiWriteRequest" });
|
|
54
|
+
export const EditorAiWriteResponseSchema = z
|
|
55
|
+
.object({
|
|
56
|
+
contentId: z.string(),
|
|
57
|
+
modifiedBuilderContent: z.record(z.string(), z.unknown()),
|
|
58
|
+
})
|
|
59
|
+
.meta({ title: "EditorAiWriteResponse" });
|
|
60
|
+
export const EditorAiErrorBodySchema = z
|
|
61
|
+
.object({
|
|
62
|
+
error: z.object({
|
|
63
|
+
code: z.string(),
|
|
64
|
+
message: z.string(),
|
|
65
|
+
hint: z.string().optional(),
|
|
66
|
+
details: z.record(z.string(), z.unknown()).optional(),
|
|
67
|
+
}),
|
|
68
|
+
})
|
|
69
|
+
.meta({ title: "EditorAiErrorBody" });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { EditorAiEditRequestSchema } from "./editor-ai.js";
|
|
3
|
+
describe("EditorAiEditRequestSchema", () => {
|
|
4
|
+
it("accepts an old_str with real content (whitespace preserved)", () => {
|
|
5
|
+
const parsed = EditorAiEditRequestSchema.parse({
|
|
6
|
+
contentId: "c1",
|
|
7
|
+
old_str: ' <Text text="hi" />',
|
|
8
|
+
new_str: ' <Text text="bye" />',
|
|
9
|
+
});
|
|
10
|
+
// Value is not trimmed — exact indentation is preserved for matching.
|
|
11
|
+
expect(parsed.old_str).toBe(' <Text text="hi" />');
|
|
12
|
+
});
|
|
13
|
+
it("rejects an empty old_str", () => {
|
|
14
|
+
const result = EditorAiEditRequestSchema.safeParse({
|
|
15
|
+
contentId: "c1",
|
|
16
|
+
old_str: "",
|
|
17
|
+
new_str: "x",
|
|
18
|
+
});
|
|
19
|
+
expect(result.success).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
it("rejects a whitespace-only old_str", () => {
|
|
22
|
+
const result = EditorAiEditRequestSchema.safeParse({
|
|
23
|
+
contentId: "c1",
|
|
24
|
+
old_str: " \n\t",
|
|
25
|
+
new_str: "x",
|
|
26
|
+
});
|
|
27
|
+
expect(result.success).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
it("allows an empty new_str (delete)", () => {
|
|
30
|
+
const result = EditorAiEditRequestSchema.safeParse({
|
|
31
|
+
contentId: "c1",
|
|
32
|
+
old_str: '<Text text="hi" />',
|
|
33
|
+
new_str: "",
|
|
34
|
+
});
|
|
35
|
+
expect(result.success).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
});
|
package/src/index.d.ts
CHANGED
|
@@ -19,4 +19,6 @@ export * from "./connectivity/types.js";
|
|
|
19
19
|
export * from "./connectivity/classify.js";
|
|
20
20
|
export * from "./single-tenancy.js";
|
|
21
21
|
export * from "./design-systems.js";
|
|
22
|
+
export * from "./editor-ai.js";
|
|
23
|
+
export * from "./realtime.js";
|
|
22
24
|
export { connectivityErrorCodeToLikelyCause, mapConnectivityErrorMessage, } from "./connectivity/error-codes.js";
|
package/src/index.js
CHANGED
|
@@ -19,4 +19,6 @@ export * from "./connectivity/types.js";
|
|
|
19
19
|
export * from "./connectivity/classify.js";
|
|
20
20
|
export * from "./single-tenancy.js";
|
|
21
21
|
export * from "./design-systems.js";
|
|
22
|
+
export * from "./editor-ai.js";
|
|
23
|
+
export * from "./realtime.js";
|
|
22
24
|
export { connectivityErrorCodeToLikelyCause, mapConnectivityErrorMessage, } from "./connectivity/error-codes.js";
|
package/src/messages.d.ts
CHANGED
|
@@ -93,6 +93,14 @@ export interface MCPServerURLDefinition {
|
|
|
93
93
|
serverId: string;
|
|
94
94
|
disabled: boolean;
|
|
95
95
|
clientName?: string;
|
|
96
|
+
/**
|
|
97
|
+
* Per-server opt-in to the LLM-driven `mcp__<name>__authenticate` flow,
|
|
98
|
+
* independent of the `codegen-tool-mcp-authenticate` LD flag. Set on the
|
|
99
|
+
* Firestore `mcpServers` doc to roll the tool out to one server at a time;
|
|
100
|
+
* the LD flag remains the org-wide switch. Either being true opts the
|
|
101
|
+
* server in, so the flag can still turn it on everywhere at once.
|
|
102
|
+
*/
|
|
103
|
+
useAuthenticateTool?: boolean;
|
|
96
104
|
}
|
|
97
105
|
export interface MCPServerToolConfiguration {
|
|
98
106
|
allowed_tools?: Array<string> | null;
|
|
@@ -119,6 +127,13 @@ export interface MCPServerDoc<TCreateDate = unknown, TLegacyTokenExpiresAt = nev
|
|
|
119
127
|
disabled?: boolean;
|
|
120
128
|
clientName: string | undefined;
|
|
121
129
|
tool_configuration?: MCPServerToolConfiguration | null;
|
|
130
|
+
/**
|
|
131
|
+
* Opt this server in to the LLM-driven `mcp__<name>__authenticate` tool
|
|
132
|
+
* instead of the blocking `mcp-auth-required` event, without turning on the
|
|
133
|
+
* org-wide `codegen-tool-mcp-authenticate` LD flag. Lets a single MCP server
|
|
134
|
+
* ship on the tool path ahead of the rest.
|
|
135
|
+
*/
|
|
136
|
+
useAuthenticateTool?: boolean;
|
|
122
137
|
oauthMetadata?: MCPServerOAuthMetadata | null;
|
|
123
138
|
authorizationToken?: string;
|
|
124
139
|
refreshToken?: string | null;
|
package/src/projects.d.ts
CHANGED
|
@@ -909,12 +909,6 @@ export interface Project {
|
|
|
909
909
|
deletedAt?: InMigrationDateNullable;
|
|
910
910
|
/** User ID of whoever deleted the project. */
|
|
911
911
|
deletedBy?: string;
|
|
912
|
-
/**
|
|
913
|
-
* When true, branches are stored in the standalone `branches` collection
|
|
914
|
-
* instead of embedded in project.branches field.
|
|
915
|
-
* Defaults to false for backwards compatibility with existing projects.
|
|
916
|
-
*/
|
|
917
|
-
useBranchesCollection?: boolean;
|
|
918
912
|
/** When true, the project is in code-only mode */
|
|
919
913
|
codeOnlyMode?: boolean;
|
|
920
914
|
/**
|
|
@@ -1075,8 +1069,6 @@ export interface CreateProjectOptions {
|
|
|
1075
1069
|
autoApplySetup?: boolean;
|
|
1076
1070
|
templateId?: string;
|
|
1077
1071
|
useInternalHost?: boolean;
|
|
1078
|
-
/** Store branches in the standalone `branches` collection vs embedded in the project doc. */
|
|
1079
|
-
useBranchesCollection?: boolean;
|
|
1080
1072
|
/** @internal Read-only source repo cloned to bootstrap the project. Carried through to Firestore. */
|
|
1081
1073
|
templateRepoUrl?: string;
|
|
1082
1074
|
/** @internal First-class hosting config copied from the template. Carried through to Firestore. */
|
|
@@ -1728,7 +1720,6 @@ export declare const CloneProjectOptionsSchema: z.ZodObject<{
|
|
|
1728
1720
|
}>;
|
|
1729
1721
|
userEmail: z.ZodOptional<z.ZodString>;
|
|
1730
1722
|
useKube: z.ZodDefault<z.ZodBoolean>;
|
|
1731
|
-
useBranchesCollection: z.ZodDefault<z.ZodBoolean>;
|
|
1732
1723
|
}, z.core.$strip>;
|
|
1733
1724
|
export type CloneProjectOptions = z.infer<typeof CloneProjectOptionsSchema>;
|
|
1734
1725
|
export declare const DuplicateBranchOptionsSchema: z.ZodObject<{
|
package/src/projects.js
CHANGED
|
@@ -640,13 +640,6 @@ export const CloneProjectOptionsSchema = z.object({
|
|
|
640
640
|
useKube: z.boolean().default(false).meta({
|
|
641
641
|
description: "Whether to provision on cloud-v2 (Kubernetes). Defaults to false.",
|
|
642
642
|
}),
|
|
643
|
-
useBranchesCollection: z
|
|
644
|
-
.boolean()
|
|
645
|
-
.default(false)
|
|
646
|
-
.meta({
|
|
647
|
-
description: "Whether the new project stores branches in the standalone `branches` " +
|
|
648
|
-
"collection rather than embedded in the project doc. Defaults to false.",
|
|
649
|
-
}),
|
|
650
643
|
});
|
|
651
644
|
export const DuplicateBranchOptionsSchema = z.object({
|
|
652
645
|
projectId: z.string().min(1).meta({
|
package/src/proxy.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export interface ProxyConfig {
|
|
|
20
20
|
*
|
|
21
21
|
* `proxyDestination` is always passed through unchanged.
|
|
22
22
|
*/
|
|
23
|
-
export declare function getProxyConfig({ devServerUrl, proxyOrigin, proxyDefaultOrigin, proxyDestination }: ProxyConfig): {
|
|
23
|
+
export declare function getProxyConfig({ devServerUrl, proxyOrigin, proxyDefaultOrigin, proxyDestination, }: ProxyConfig): {
|
|
24
24
|
proxyOrigin: string | undefined;
|
|
25
25
|
proxyDefaultOrigin: string | undefined;
|
|
26
26
|
proxyDestination: string | undefined;
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const BUILDER_REALTIME_MODEL: "gpt-realtime-2.1";
|
|
3
|
+
export declare const BUILDER_REALTIME_MAX_SDP_LENGTH = 256000;
|
|
4
|
+
export declare const BUILDER_REALTIME_MAX_SESSION_BYTES = 64000;
|
|
5
|
+
export declare const OpenAIRealtimeFunctionToolSchema: z.ZodObject<{
|
|
6
|
+
type: z.ZodLiteral<"function">;
|
|
7
|
+
name: z.ZodString;
|
|
8
|
+
description: z.ZodOptional<z.ZodString>;
|
|
9
|
+
parameters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
10
|
+
strict: z.ZodOptional<z.ZodBoolean>;
|
|
11
|
+
}, z.core.$strip>;
|
|
12
|
+
export type OpenAIRealtimeFunctionTool = z.infer<typeof OpenAIRealtimeFunctionToolSchema>;
|
|
13
|
+
export declare const OpenAIRealtimeMcpToolSchema: z.ZodObject<{
|
|
14
|
+
type: z.ZodLiteral<"mcp">;
|
|
15
|
+
server_label: z.ZodString;
|
|
16
|
+
server_url: z.ZodOptional<z.ZodURL>;
|
|
17
|
+
server_description: z.ZodOptional<z.ZodString>;
|
|
18
|
+
authorization: z.ZodOptional<z.ZodString>;
|
|
19
|
+
allowed_tools: z.ZodOptional<z.ZodUnion<readonly [z.ZodArray<z.ZodString>, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
|
|
20
|
+
require_approval: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
|
|
21
|
+
always: "always";
|
|
22
|
+
never: "never";
|
|
23
|
+
}>, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
|
|
24
|
+
}, z.core.$strip>;
|
|
25
|
+
export type OpenAIRealtimeMcpTool = z.infer<typeof OpenAIRealtimeMcpToolSchema>;
|
|
26
|
+
export declare const OpenAIRealtimeToolSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
27
|
+
type: z.ZodLiteral<"function">;
|
|
28
|
+
name: z.ZodString;
|
|
29
|
+
description: z.ZodOptional<z.ZodString>;
|
|
30
|
+
parameters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
31
|
+
strict: z.ZodOptional<z.ZodBoolean>;
|
|
32
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
33
|
+
type: z.ZodLiteral<"mcp">;
|
|
34
|
+
server_label: z.ZodString;
|
|
35
|
+
server_url: z.ZodOptional<z.ZodURL>;
|
|
36
|
+
server_description: z.ZodOptional<z.ZodString>;
|
|
37
|
+
authorization: z.ZodOptional<z.ZodString>;
|
|
38
|
+
allowed_tools: z.ZodOptional<z.ZodUnion<readonly [z.ZodArray<z.ZodString>, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
|
|
39
|
+
require_approval: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
|
|
40
|
+
always: "always";
|
|
41
|
+
never: "never";
|
|
42
|
+
}>, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
|
|
43
|
+
}, z.core.$strip>], "type">;
|
|
44
|
+
export type OpenAIRealtimeTool = z.infer<typeof OpenAIRealtimeToolSchema>;
|
|
45
|
+
/**
|
|
46
|
+
* The subset of OpenAI's Realtime session configuration accepted by Builder
|
|
47
|
+
* Connect. Zod objects intentionally strip unknown keys before this config is
|
|
48
|
+
* forwarded to OpenAI.
|
|
49
|
+
*/
|
|
50
|
+
export declare const OpenAIRealtimeSessionConfigSchema: z.ZodObject<{
|
|
51
|
+
type: z.ZodLiteral<"realtime">;
|
|
52
|
+
model: z.ZodLiteral<"gpt-realtime-2.1">;
|
|
53
|
+
output_modalities: z.ZodTuple<[z.ZodLiteral<"audio">], null>;
|
|
54
|
+
instructions: z.ZodOptional<z.ZodString>;
|
|
55
|
+
audio: z.ZodOptional<z.ZodObject<{
|
|
56
|
+
input: z.ZodOptional<z.ZodObject<{
|
|
57
|
+
format: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
|
|
58
|
+
noise_reduction: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
59
|
+
type: z.ZodEnum<{
|
|
60
|
+
far_field: "far_field";
|
|
61
|
+
near_field: "near_field";
|
|
62
|
+
}>;
|
|
63
|
+
}, z.core.$strip>>>;
|
|
64
|
+
transcription: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
65
|
+
model: z.ZodLiteral<"gpt-4o-mini-transcribe">;
|
|
66
|
+
language: z.ZodOptional<z.ZodString>;
|
|
67
|
+
prompt: z.ZodOptional<z.ZodString>;
|
|
68
|
+
}, z.core.$strip>>>;
|
|
69
|
+
turn_detection: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
70
|
+
type: z.ZodEnum<{
|
|
71
|
+
semantic_vad: "semantic_vad";
|
|
72
|
+
server_vad: "server_vad";
|
|
73
|
+
}>;
|
|
74
|
+
threshold: z.ZodOptional<z.ZodNumber>;
|
|
75
|
+
prefix_padding_ms: z.ZodOptional<z.ZodNumber>;
|
|
76
|
+
silence_duration_ms: z.ZodOptional<z.ZodNumber>;
|
|
77
|
+
idle_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
78
|
+
eagerness: z.ZodOptional<z.ZodEnum<{
|
|
79
|
+
auto: "auto";
|
|
80
|
+
high: "high";
|
|
81
|
+
low: "low";
|
|
82
|
+
medium: "medium";
|
|
83
|
+
}>>;
|
|
84
|
+
create_response: z.ZodOptional<z.ZodBoolean>;
|
|
85
|
+
interrupt_response: z.ZodOptional<z.ZodBoolean>;
|
|
86
|
+
}, z.core.$strip>>>;
|
|
87
|
+
}, z.core.$strip>>;
|
|
88
|
+
output: z.ZodOptional<z.ZodObject<{
|
|
89
|
+
format: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
|
|
90
|
+
voice: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
91
|
+
id: z.ZodString;
|
|
92
|
+
}, z.core.$strip>]>>;
|
|
93
|
+
speed: z.ZodOptional<z.ZodNumber>;
|
|
94
|
+
}, z.core.$strip>>;
|
|
95
|
+
}, z.core.$strip>>;
|
|
96
|
+
tools: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
97
|
+
type: z.ZodLiteral<"function">;
|
|
98
|
+
name: z.ZodString;
|
|
99
|
+
description: z.ZodOptional<z.ZodString>;
|
|
100
|
+
parameters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
101
|
+
strict: z.ZodOptional<z.ZodBoolean>;
|
|
102
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
103
|
+
type: z.ZodLiteral<"mcp">;
|
|
104
|
+
server_label: z.ZodString;
|
|
105
|
+
server_url: z.ZodOptional<z.ZodURL>;
|
|
106
|
+
server_description: z.ZodOptional<z.ZodString>;
|
|
107
|
+
authorization: z.ZodOptional<z.ZodString>;
|
|
108
|
+
allowed_tools: z.ZodOptional<z.ZodUnion<readonly [z.ZodArray<z.ZodString>, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
|
|
109
|
+
require_approval: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
|
|
110
|
+
always: "always";
|
|
111
|
+
never: "never";
|
|
112
|
+
}>, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
|
|
113
|
+
}, z.core.$strip>], "type">>>;
|
|
114
|
+
tool_choice: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
|
|
115
|
+
auto: "auto";
|
|
116
|
+
none: "none";
|
|
117
|
+
required: "required";
|
|
118
|
+
}>, z.ZodObject<{
|
|
119
|
+
type: z.ZodLiteral<"function">;
|
|
120
|
+
name: z.ZodString;
|
|
121
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
122
|
+
type: z.ZodLiteral<"mcp">;
|
|
123
|
+
server_label: z.ZodString;
|
|
124
|
+
name: z.ZodOptional<z.ZodString>;
|
|
125
|
+
}, z.core.$strip>]>>;
|
|
126
|
+
max_output_tokens: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"inf">, z.ZodNumber]>>;
|
|
127
|
+
prompt: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
128
|
+
id: z.ZodString;
|
|
129
|
+
version: z.ZodOptional<z.ZodString>;
|
|
130
|
+
variables: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
131
|
+
}, z.core.$strip>>>;
|
|
132
|
+
tracing: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<"auto">, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>>;
|
|
133
|
+
truncation: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
|
|
134
|
+
auto: "auto";
|
|
135
|
+
disabled: "disabled";
|
|
136
|
+
}>, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
|
|
137
|
+
include: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
138
|
+
}, z.core.$strip>;
|
|
139
|
+
export type OpenAIRealtimeSessionConfig = z.infer<typeof OpenAIRealtimeSessionConfigSchema>;
|
|
140
|
+
export declare const BuilderRealtimeSessionRequestSchema: z.ZodObject<{
|
|
141
|
+
sdp: z.ZodString;
|
|
142
|
+
session: z.ZodObject<{
|
|
143
|
+
type: z.ZodLiteral<"realtime">;
|
|
144
|
+
model: z.ZodLiteral<"gpt-realtime-2.1">;
|
|
145
|
+
output_modalities: z.ZodTuple<[z.ZodLiteral<"audio">], null>;
|
|
146
|
+
instructions: z.ZodOptional<z.ZodString>;
|
|
147
|
+
audio: z.ZodOptional<z.ZodObject<{
|
|
148
|
+
input: z.ZodOptional<z.ZodObject<{
|
|
149
|
+
format: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
|
|
150
|
+
noise_reduction: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
151
|
+
type: z.ZodEnum<{
|
|
152
|
+
far_field: "far_field";
|
|
153
|
+
near_field: "near_field";
|
|
154
|
+
}>;
|
|
155
|
+
}, z.core.$strip>>>;
|
|
156
|
+
transcription: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
157
|
+
model: z.ZodLiteral<"gpt-4o-mini-transcribe">;
|
|
158
|
+
language: z.ZodOptional<z.ZodString>;
|
|
159
|
+
prompt: z.ZodOptional<z.ZodString>;
|
|
160
|
+
}, z.core.$strip>>>;
|
|
161
|
+
turn_detection: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
162
|
+
type: z.ZodEnum<{
|
|
163
|
+
semantic_vad: "semantic_vad";
|
|
164
|
+
server_vad: "server_vad";
|
|
165
|
+
}>;
|
|
166
|
+
threshold: z.ZodOptional<z.ZodNumber>;
|
|
167
|
+
prefix_padding_ms: z.ZodOptional<z.ZodNumber>;
|
|
168
|
+
silence_duration_ms: z.ZodOptional<z.ZodNumber>;
|
|
169
|
+
idle_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
170
|
+
eagerness: z.ZodOptional<z.ZodEnum<{
|
|
171
|
+
auto: "auto";
|
|
172
|
+
high: "high";
|
|
173
|
+
low: "low";
|
|
174
|
+
medium: "medium";
|
|
175
|
+
}>>;
|
|
176
|
+
create_response: z.ZodOptional<z.ZodBoolean>;
|
|
177
|
+
interrupt_response: z.ZodOptional<z.ZodBoolean>;
|
|
178
|
+
}, z.core.$strip>>>;
|
|
179
|
+
}, z.core.$strip>>;
|
|
180
|
+
output: z.ZodOptional<z.ZodObject<{
|
|
181
|
+
format: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
|
|
182
|
+
voice: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
183
|
+
id: z.ZodString;
|
|
184
|
+
}, z.core.$strip>]>>;
|
|
185
|
+
speed: z.ZodOptional<z.ZodNumber>;
|
|
186
|
+
}, z.core.$strip>>;
|
|
187
|
+
}, z.core.$strip>>;
|
|
188
|
+
tools: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
189
|
+
type: z.ZodLiteral<"function">;
|
|
190
|
+
name: z.ZodString;
|
|
191
|
+
description: z.ZodOptional<z.ZodString>;
|
|
192
|
+
parameters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
193
|
+
strict: z.ZodOptional<z.ZodBoolean>;
|
|
194
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
195
|
+
type: z.ZodLiteral<"mcp">;
|
|
196
|
+
server_label: z.ZodString;
|
|
197
|
+
server_url: z.ZodOptional<z.ZodURL>;
|
|
198
|
+
server_description: z.ZodOptional<z.ZodString>;
|
|
199
|
+
authorization: z.ZodOptional<z.ZodString>;
|
|
200
|
+
allowed_tools: z.ZodOptional<z.ZodUnion<readonly [z.ZodArray<z.ZodString>, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
|
|
201
|
+
require_approval: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
|
|
202
|
+
always: "always";
|
|
203
|
+
never: "never";
|
|
204
|
+
}>, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
|
|
205
|
+
}, z.core.$strip>], "type">>>;
|
|
206
|
+
tool_choice: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
|
|
207
|
+
auto: "auto";
|
|
208
|
+
none: "none";
|
|
209
|
+
required: "required";
|
|
210
|
+
}>, z.ZodObject<{
|
|
211
|
+
type: z.ZodLiteral<"function">;
|
|
212
|
+
name: z.ZodString;
|
|
213
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
214
|
+
type: z.ZodLiteral<"mcp">;
|
|
215
|
+
server_label: z.ZodString;
|
|
216
|
+
name: z.ZodOptional<z.ZodString>;
|
|
217
|
+
}, z.core.$strip>]>>;
|
|
218
|
+
max_output_tokens: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"inf">, z.ZodNumber]>>;
|
|
219
|
+
prompt: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
220
|
+
id: z.ZodString;
|
|
221
|
+
version: z.ZodOptional<z.ZodString>;
|
|
222
|
+
variables: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
223
|
+
}, z.core.$strip>>>;
|
|
224
|
+
tracing: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<"auto">, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>>;
|
|
225
|
+
truncation: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
|
|
226
|
+
auto: "auto";
|
|
227
|
+
disabled: "disabled";
|
|
228
|
+
}>, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
|
|
229
|
+
include: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
230
|
+
}, z.core.$strip>;
|
|
231
|
+
}, z.core.$strip>;
|
|
232
|
+
export type BuilderRealtimeSessionRequest = z.infer<typeof BuilderRealtimeSessionRequestSchema>;
|
package/src/realtime.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const BUILDER_REALTIME_MODEL = "gpt-realtime-2.1";
|
|
3
|
+
export const BUILDER_REALTIME_MAX_SDP_LENGTH = 256000;
|
|
4
|
+
export const BUILDER_REALTIME_MAX_SESSION_BYTES = 64000;
|
|
5
|
+
const utf8ByteLength = (value) => new TextEncoder().encode(value).length;
|
|
6
|
+
const boundedRecord = (maxBytes, message) => z.record(z.string(), z.unknown()).refine((value) => {
|
|
7
|
+
try {
|
|
8
|
+
return utf8ByteLength(JSON.stringify(value)) <= maxBytes;
|
|
9
|
+
}
|
|
10
|
+
catch (_a) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}, { message });
|
|
14
|
+
const RealtimeAudioFormatSchema = z.union([
|
|
15
|
+
z.string().min(1).max(128),
|
|
16
|
+
boundedRecord(2048, "Audio format configuration is too large"),
|
|
17
|
+
]);
|
|
18
|
+
const RealtimeInputAudioSchema = z.object({
|
|
19
|
+
format: RealtimeAudioFormatSchema.optional(),
|
|
20
|
+
noise_reduction: z
|
|
21
|
+
.object({
|
|
22
|
+
type: z.enum(["near_field", "far_field"]),
|
|
23
|
+
})
|
|
24
|
+
.nullable()
|
|
25
|
+
.optional(),
|
|
26
|
+
transcription: z
|
|
27
|
+
.object({
|
|
28
|
+
model: z.literal("gpt-4o-mini-transcribe"),
|
|
29
|
+
language: z.string().min(1).max(32).optional(),
|
|
30
|
+
prompt: z.string().max(4096).optional(),
|
|
31
|
+
})
|
|
32
|
+
.nullable()
|
|
33
|
+
.optional(),
|
|
34
|
+
turn_detection: z
|
|
35
|
+
.object({
|
|
36
|
+
type: z.enum(["server_vad", "semantic_vad"]),
|
|
37
|
+
threshold: z.number().min(0).max(1).optional(),
|
|
38
|
+
prefix_padding_ms: z.number().int().min(0).max(10000).optional(),
|
|
39
|
+
silence_duration_ms: z.number().int().min(0).max(60000).optional(),
|
|
40
|
+
idle_timeout_ms: z.number().int().min(0).max(3600000).optional(),
|
|
41
|
+
eagerness: z.enum(["low", "medium", "high", "auto"]).optional(),
|
|
42
|
+
create_response: z.boolean().optional(),
|
|
43
|
+
interrupt_response: z.boolean().optional(),
|
|
44
|
+
})
|
|
45
|
+
.nullable()
|
|
46
|
+
.optional(),
|
|
47
|
+
});
|
|
48
|
+
const RealtimeOutputAudioSchema = z.object({
|
|
49
|
+
format: RealtimeAudioFormatSchema.optional(),
|
|
50
|
+
voice: z
|
|
51
|
+
.union([
|
|
52
|
+
z.string().min(1).max(128),
|
|
53
|
+
z.object({ id: z.string().min(1).max(256) }),
|
|
54
|
+
])
|
|
55
|
+
.optional(),
|
|
56
|
+
speed: z.number().positive().max(4).optional(),
|
|
57
|
+
});
|
|
58
|
+
export const OpenAIRealtimeFunctionToolSchema = z.object({
|
|
59
|
+
type: z.literal("function"),
|
|
60
|
+
name: z
|
|
61
|
+
.string()
|
|
62
|
+
.min(1)
|
|
63
|
+
.max(64)
|
|
64
|
+
.regex(/^[a-zA-Z0-9_-]+$/),
|
|
65
|
+
description: z.string().max(4096).optional(),
|
|
66
|
+
parameters: boundedRecord(32000, "Realtime tool parameter schema is too large").optional(),
|
|
67
|
+
strict: z.boolean().optional(),
|
|
68
|
+
});
|
|
69
|
+
export const OpenAIRealtimeMcpToolSchema = z.object({
|
|
70
|
+
type: z.literal("mcp"),
|
|
71
|
+
server_label: z.string().min(1).max(128),
|
|
72
|
+
server_url: z.url().max(2048).optional(),
|
|
73
|
+
server_description: z.string().max(4096).optional(),
|
|
74
|
+
authorization: z.string().min(1).max(8192).optional(),
|
|
75
|
+
allowed_tools: z
|
|
76
|
+
.union([
|
|
77
|
+
z.array(z.string().min(1).max(128)).max(64),
|
|
78
|
+
boundedRecord(8000, "MCP allowed-tools configuration is too large"),
|
|
79
|
+
])
|
|
80
|
+
.optional(),
|
|
81
|
+
require_approval: z
|
|
82
|
+
.union([
|
|
83
|
+
z.enum(["always", "never"]),
|
|
84
|
+
boundedRecord(8000, "MCP approval configuration is too large"),
|
|
85
|
+
])
|
|
86
|
+
.optional(),
|
|
87
|
+
});
|
|
88
|
+
export const OpenAIRealtimeToolSchema = z.discriminatedUnion("type", [
|
|
89
|
+
OpenAIRealtimeFunctionToolSchema,
|
|
90
|
+
OpenAIRealtimeMcpToolSchema,
|
|
91
|
+
]);
|
|
92
|
+
const OpenAIRealtimeToolChoiceSchema = z.union([
|
|
93
|
+
z.enum(["auto", "none", "required"]),
|
|
94
|
+
z.object({
|
|
95
|
+
type: z.literal("function"),
|
|
96
|
+
name: z.string().min(1).max(64),
|
|
97
|
+
}),
|
|
98
|
+
z.object({
|
|
99
|
+
type: z.literal("mcp"),
|
|
100
|
+
server_label: z.string().min(1).max(128),
|
|
101
|
+
name: z.string().min(1).max(128).optional(),
|
|
102
|
+
}),
|
|
103
|
+
]);
|
|
104
|
+
/**
|
|
105
|
+
* The subset of OpenAI's Realtime session configuration accepted by Builder
|
|
106
|
+
* Connect. Zod objects intentionally strip unknown keys before this config is
|
|
107
|
+
* forwarded to OpenAI.
|
|
108
|
+
*/
|
|
109
|
+
export const OpenAIRealtimeSessionConfigSchema = z
|
|
110
|
+
.object({
|
|
111
|
+
type: z.literal("realtime"),
|
|
112
|
+
model: z.literal(BUILDER_REALTIME_MODEL),
|
|
113
|
+
output_modalities: z.tuple([z.literal("audio")]),
|
|
114
|
+
instructions: z.string().max(32000).optional(),
|
|
115
|
+
audio: z
|
|
116
|
+
.object({
|
|
117
|
+
input: RealtimeInputAudioSchema.optional(),
|
|
118
|
+
output: RealtimeOutputAudioSchema.optional(),
|
|
119
|
+
})
|
|
120
|
+
.optional(),
|
|
121
|
+
tools: z.array(OpenAIRealtimeToolSchema).max(32).optional(),
|
|
122
|
+
tool_choice: OpenAIRealtimeToolChoiceSchema.optional(),
|
|
123
|
+
max_output_tokens: z
|
|
124
|
+
.union([z.literal("inf"), z.number().int().min(1).max(4096)])
|
|
125
|
+
.optional(),
|
|
126
|
+
prompt: z
|
|
127
|
+
.object({
|
|
128
|
+
id: z.string().min(1).max(256),
|
|
129
|
+
version: z.string().min(1).max(128).optional(),
|
|
130
|
+
variables: boundedRecord(16000, "Realtime prompt variables are too large").optional(),
|
|
131
|
+
})
|
|
132
|
+
.nullable()
|
|
133
|
+
.optional(),
|
|
134
|
+
tracing: z
|
|
135
|
+
.union([
|
|
136
|
+
z.literal("auto"),
|
|
137
|
+
boundedRecord(8000, "Realtime tracing configuration is too large"),
|
|
138
|
+
])
|
|
139
|
+
.nullable()
|
|
140
|
+
.optional(),
|
|
141
|
+
truncation: z
|
|
142
|
+
.union([
|
|
143
|
+
z.enum(["auto", "disabled"]),
|
|
144
|
+
boundedRecord(2048, "Realtime truncation configuration is too large"),
|
|
145
|
+
])
|
|
146
|
+
.optional(),
|
|
147
|
+
include: z.array(z.string().min(1).max(256)).max(16).optional(),
|
|
148
|
+
})
|
|
149
|
+
.refine((session) => utf8ByteLength(JSON.stringify(session)) <=
|
|
150
|
+
BUILDER_REALTIME_MAX_SESSION_BYTES, { message: "Realtime session configuration is too large" });
|
|
151
|
+
export const BuilderRealtimeSessionRequestSchema = z.object({
|
|
152
|
+
sdp: z
|
|
153
|
+
.string()
|
|
154
|
+
.min(1)
|
|
155
|
+
.max(BUILDER_REALTIME_MAX_SDP_LENGTH)
|
|
156
|
+
.refine((sdp) => sdp.trim().length > 0, { message: "SDP cannot be blank" }),
|
|
157
|
+
session: OpenAIRealtimeSessionConfigSchema,
|
|
158
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { BUILDER_REALTIME_MAX_SDP_LENGTH, BuilderRealtimeSessionRequestSchema, OpenAIRealtimeSessionConfigSchema, } from "./realtime.js";
|
|
3
|
+
const validSession = {
|
|
4
|
+
type: "realtime",
|
|
5
|
+
model: "gpt-realtime-2.1",
|
|
6
|
+
output_modalities: ["audio"],
|
|
7
|
+
instructions: "Help the user operate their Builder app.",
|
|
8
|
+
};
|
|
9
|
+
describe("BuilderRealtimeSessionRequestSchema", () => {
|
|
10
|
+
it("accepts a bounded audio session with tools", () => {
|
|
11
|
+
var _a;
|
|
12
|
+
const result = BuilderRealtimeSessionRequestSchema.parse({
|
|
13
|
+
sdp: "v=0\r\no=- 1 1 IN IP4 127.0.0.1",
|
|
14
|
+
session: {
|
|
15
|
+
...validSession,
|
|
16
|
+
audio: {
|
|
17
|
+
input: {
|
|
18
|
+
turn_detection: {
|
|
19
|
+
type: "semantic_vad",
|
|
20
|
+
eagerness: "auto",
|
|
21
|
+
create_response: true,
|
|
22
|
+
interrupt_response: true,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
output: { voice: "marin" },
|
|
26
|
+
},
|
|
27
|
+
tools: [
|
|
28
|
+
{
|
|
29
|
+
type: "function",
|
|
30
|
+
name: "navigate",
|
|
31
|
+
description: "Navigate within the app",
|
|
32
|
+
parameters: {
|
|
33
|
+
type: "object",
|
|
34
|
+
properties: { path: { type: "string" } },
|
|
35
|
+
required: ["path"],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
expect(result.session.model).toBe("gpt-realtime-2.1");
|
|
42
|
+
expect((_a = result.session.tools) === null || _a === void 0 ? void 0 : _a[0]).toMatchObject({
|
|
43
|
+
type: "function",
|
|
44
|
+
name: "navigate",
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
it("strips unknown request, session, and known nested config keys", () => {
|
|
48
|
+
const sdp = " v=0\r\n";
|
|
49
|
+
const result = BuilderRealtimeSessionRequestSchema.parse({
|
|
50
|
+
sdp,
|
|
51
|
+
ignoredRequestValue: "secret",
|
|
52
|
+
session: {
|
|
53
|
+
...validSession,
|
|
54
|
+
ignoredSessionValue: "secret",
|
|
55
|
+
audio: {
|
|
56
|
+
output: {
|
|
57
|
+
voice: "marin",
|
|
58
|
+
ignoredAudioValue: "secret",
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
expect(result).toEqual({
|
|
64
|
+
sdp,
|
|
65
|
+
session: {
|
|
66
|
+
...validSession,
|
|
67
|
+
audio: { output: { voice: "marin" } },
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
it.each([
|
|
72
|
+
[{ ...validSession, type: "transcription" }],
|
|
73
|
+
[{ ...validSession, model: "gpt-realtime" }],
|
|
74
|
+
[{ ...validSession, output_modalities: ["text"] }],
|
|
75
|
+
[{ ...validSession, output_modalities: ["audio", "text"] }],
|
|
76
|
+
[{ ...validSession, output_modalities: [] }],
|
|
77
|
+
])("rejects unsupported sessions", (session) => {
|
|
78
|
+
expect(BuilderRealtimeSessionRequestSchema.safeParse({ sdp: "v=0", session })
|
|
79
|
+
.success).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
it("rejects transcription models that do not match the metered rate", () => {
|
|
82
|
+
expect(OpenAIRealtimeSessionConfigSchema.safeParse({
|
|
83
|
+
...validSession,
|
|
84
|
+
audio: {
|
|
85
|
+
input: { transcription: { model: "gpt-4o-transcribe" } },
|
|
86
|
+
},
|
|
87
|
+
}).success).toBe(false);
|
|
88
|
+
});
|
|
89
|
+
it("rejects empty and oversized SDP offers", () => {
|
|
90
|
+
expect(BuilderRealtimeSessionRequestSchema.safeParse({
|
|
91
|
+
sdp: " ",
|
|
92
|
+
session: validSession,
|
|
93
|
+
}).success).toBe(false);
|
|
94
|
+
expect(BuilderRealtimeSessionRequestSchema.safeParse({
|
|
95
|
+
sdp: "x".repeat(BUILDER_REALTIME_MAX_SDP_LENGTH + 1),
|
|
96
|
+
session: validSession,
|
|
97
|
+
}).success).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
it("bounds instructions, tool count, and tool parameter schemas", () => {
|
|
100
|
+
expect(OpenAIRealtimeSessionConfigSchema.safeParse({
|
|
101
|
+
...validSession,
|
|
102
|
+
instructions: "x".repeat(32001),
|
|
103
|
+
}).success).toBe(false);
|
|
104
|
+
expect(OpenAIRealtimeSessionConfigSchema.safeParse({
|
|
105
|
+
...validSession,
|
|
106
|
+
tools: Array.from({ length: 33 }, (_, index) => ({
|
|
107
|
+
type: "function",
|
|
108
|
+
name: `tool_${index}`,
|
|
109
|
+
})),
|
|
110
|
+
}).success).toBe(false);
|
|
111
|
+
expect(OpenAIRealtimeSessionConfigSchema.safeParse({
|
|
112
|
+
...validSession,
|
|
113
|
+
tools: [
|
|
114
|
+
{
|
|
115
|
+
type: "function",
|
|
116
|
+
name: "large_tool",
|
|
117
|
+
parameters: { description: "x".repeat(32001) },
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
}).success).toBe(false);
|
|
121
|
+
});
|
|
122
|
+
});
|