@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@builder.io/ai-utils",
3
- "version": "0.81.3",
3
+ "version": "0.82.0",
4
4
  "description": "Builder.io AI utils",
5
5
  "files": [
6
6
  "src"
@@ -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";
@@ -99,7 +99,8 @@ export interface BuilderEditorState {
99
99
  */
100
100
  content?: BuilderContent;
101
101
  /**
102
- * Builder custom components.
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>;
@@ -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>;
@@ -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
+ });