@builder.io/ai-utils 0.99.0 → 0.100.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/src/projects.js CHANGED
@@ -308,18 +308,37 @@ export const ShapeAnswerSchema = z.object({
308
308
  llmOptionId: z.string().optional(),
309
309
  guessOptionId: z.string().optional(),
310
310
  });
311
+ export const ShapeQuestionDecisionSchema = z.object({
312
+ questionId: z.string(),
313
+ ask: z.boolean(),
314
+ llmOptionId: z.string().optional(),
315
+ confidence: z.number().optional(),
316
+ guessOptionId: z.string(),
317
+ });
311
318
  export const ShapeSessionSchema = z.object({
312
319
  questionnaireId: z.string(),
313
320
  catalogVersion: z.string().regex(/^\d{4}-\d{2}-\d{2}\.\d+$/),
314
321
  source: z.enum(["static", "agent"]),
315
322
  status: z.enum(["assembling", "pending", "resolved"]),
316
- selection: z.enum(["llm", "fallback"]),
323
+ selection: z.enum(["llm", "fallback"]).optional(),
324
+ decisions: z.array(ShapeQuestionDecisionSchema).optional(),
317
325
  questions: z.array(ShapeQuestionSchema),
318
326
  initialPromptId: z.string(),
319
327
  answers: z.array(ShapeAnswerSchema).optional(),
328
+ issuedAt: z.string().optional(),
320
329
  assembledAt: z.string().optional(),
321
330
  resolvedAt: z.string().optional(),
322
331
  });
332
+ export const InitialPromptSchema = z.object({
333
+ id: z.string(),
334
+ projectId: z.string(),
335
+ branchName: z.string().optional(),
336
+ prompt: z.string(),
337
+ attachmentIds: z.array(z.string()).optional(),
338
+ createdBy: z.string(),
339
+ createdAt: z.number(),
340
+ deliveredAt: z.number().optional(),
341
+ });
323
342
  export const ProjectRolePermissionsSchema = z
324
343
  .object({
325
344
  view: z.boolean().optional(),
@@ -861,8 +880,9 @@ export const getBranchState = (branch) => {
861
880
  * Get the state of a project, checking `state` first and falling back to `deleted` for backwards compatibility.
862
881
  */
863
882
  export const getProjectState = (project) => {
864
- var _a;
865
- return (_a = project.state) !== null && _a !== void 0 ? _a : "active";
883
+ if (project.state)
884
+ return project.state;
885
+ return project.deleted ? "deleted" : "active";
866
886
  };
867
887
  /**
868
888
  * Check if a branch is deleted, supporting both `state` and legacy `deleted` fields.
@@ -1753,6 +1773,17 @@ export const GetHostingAiUsageResponseSchema = z.object({
1753
1773
  */
1754
1774
  coreSupportsGateway: z.boolean(),
1755
1775
  token: HostingAiUsageTokenStatusSchema.nullable(),
1776
+ /**
1777
+ * The `AI_USAGE_MANAGED_PROD_ENV_KEYS` currently holding a value the deployed
1778
+ * site will see, names only. The client cannot derive this: the write endpoint
1779
+ * deliberately leaves the caller's project model stale, so anything projected
1780
+ * locally is wrong the moment two writes or an out-of-band env edit interleave.
1781
+ *
1782
+ * Absent on an API that predates the field, which a client must read as
1783
+ * "unknown" and not as "none": reading it as none pre-selects a mode whose
1784
+ * write deletes the customer's provider key.
1785
+ */
1786
+ managedProdEnvKeys: z.array(z.string()).optional(),
1756
1787
  });
1757
1788
  /** Providers a customer may bring their own key for. */
1758
1789
  export const AiUsageByokProviderSchema = z.enum(["anthropic", "openai"]);
@@ -1770,6 +1801,7 @@ export const SetHostingAiUsageRequestSchema = z.strictObject({
1770
1801
  mode: AiPaymentModeSchema,
1771
1802
  provider: AiUsageByokProviderSchema.optional(),
1772
1803
  apiKey: z.string().min(1).optional(),
1804
+ reuseExistingKey: z.boolean().optional(),
1773
1805
  // https only: this value is written into a published site's prod env as
1774
1806
  // `OPENAI_BASE_URL`, so an `http://` endpoint would send the customer's
1775
1807
  // provider key and every prompt in plaintext from the deployed site.
@@ -1789,6 +1821,14 @@ export const SetHostingAiUsageResponseSchema = z.object({
1789
1821
  * mode), which is exactly the case where no redeploy is needed.
1790
1822
  */
1791
1823
  configUpdatedAt: z.number().optional(),
1824
+ /**
1825
+ * The `AI_USAGE_MANAGED_PROD_ENV_KEYS` present in prod env after the write.
1826
+ * The client cannot derive this: its project model is deliberately not updated
1827
+ * with the rewrite, so projecting it locally goes stale the moment two writes
1828
+ * or an out-of-band env edit interleave. Optional so a client can still talk to
1829
+ * an API that predates it.
1830
+ */
1831
+ managedProdEnvKeys: z.array(z.string()).optional(),
1792
1832
  });
1793
1833
  /** Body for `POST /projects/hosting/ai-usage/revoke`. */
1794
1834
  export const RevokeHostingAiTokenRequestSchema = z.strictObject({
@@ -2120,17 +2160,58 @@ export const ProjectTemplateOverridesSchema = z.object({
2120
2160
  settings: ProjectSettingsExternalSchema.partial().optional(),
2121
2161
  hosting: ProjectHostingExternalSchema.optional(),
2122
2162
  });
2123
- export const CreateProjectTemplateFromProjectOptionsSchema = z.object({
2124
- sourceProjectId: z.string().min(1).meta({
2125
- description: "Project whose settings, repo fields, and hosting config seed the template.",
2126
- }),
2127
- sourceBranchName: z.string().min(1).meta({
2128
- description: "Branch of the source project that projects created from this template are seeded from.",
2163
+ /** Mirrors `ProjectSourceSchema`'s repo/template split for project creation. */
2164
+ export const CreateTemplateSourceSchema = z.discriminatedUnion("kind", [
2165
+ z.object({
2166
+ kind: z.literal("project"),
2167
+ sourceProjectId: z.string().min(1).meta({
2168
+ description: "Project whose settings, repo fields, and hosting config seed the template.",
2169
+ }),
2170
+ sourceBranchName: z.string().min(1).meta({
2171
+ description: "Branch of the source project that projects created from this template are seeded from.",
2172
+ }),
2129
2173
  }),
2130
- overrides: ProjectTemplateOverridesSchema.optional().meta({
2131
- description: "Fields to override on top of the values copied from the source project.",
2174
+ z.object({
2175
+ kind: z.literal("template"),
2176
+ templateId: z.string().min(1).meta({
2177
+ description: "Built-in stub or custom template id whose settings, repo fields, and hosting config seed the new template.",
2178
+ }),
2132
2179
  }),
2180
+ ]);
2181
+ /**
2182
+ * Deprecation-window shim for callers still sending the old flat
2183
+ * `{ sourceProjectId, sourceBranchName }` shape — safe to delete once no
2184
+ * caller sends it anymore.
2185
+ */
2186
+ const LegacyFlatTemplateSourceSchema = z.object({
2187
+ sourceProjectId: z.string().min(1),
2188
+ sourceBranchName: z.string().min(1),
2133
2189
  });
2190
+ export const CreateProjectTemplateFromProjectOptionsSchema = z
2191
+ .union([
2192
+ z.object({
2193
+ source: CreateTemplateSourceSchema.meta({
2194
+ description: "The project or template whose settings, repo fields, and hosting config seed the new template.",
2195
+ }),
2196
+ overrides: ProjectTemplateOverridesSchema.optional().meta({
2197
+ description: "Fields to override on top of the values copied from the source.",
2198
+ }),
2199
+ }),
2200
+ z.object({
2201
+ ...LegacyFlatTemplateSourceSchema.shape,
2202
+ overrides: ProjectTemplateOverridesSchema.optional(),
2203
+ }),
2204
+ ])
2205
+ .transform((parsed) => "source" in parsed
2206
+ ? parsed
2207
+ : {
2208
+ source: {
2209
+ kind: "project",
2210
+ sourceProjectId: parsed.sourceProjectId,
2211
+ sourceBranchName: parsed.sourceBranchName,
2212
+ },
2213
+ overrides: parsed.overrides,
2214
+ });
2134
2215
  /**
2135
2216
  * Deliberately minimal: the stored template carries plaintext secret env values,
2136
2217
  * so nothing beyond the identity of the new template goes back to the caller.
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { AGENT_NATIVE_STARTER_REPO, getAgentNativeRootPackageJsonWriteError, getManagedDatabasePackageJsonError, matchesAgentNativeStarter, ProjectHostingExternalSchema, ProjectHostingSchema, ProjectSettingsExternalSchema, ProjectSettingsSchema, ProjectTemplateOverridesSchema, ShapeSessionSchema, } from "./projects";
2
+ import { AGENT_NATIVE_STARTER_REPO, getAgentNativeRootPackageJsonWriteError, getManagedDatabasePackageJsonError, getProjectState, InitialPromptSchema, isProjectDeleted, matchesAgentNativeStarter, ProjectHostingExternalSchema, ProjectHostingSchema, ProjectSettingsExternalSchema, ProjectSettingsSchema, ProjectTemplateOverridesSchema, ShapeSessionSchema, } from "./projects";
3
3
  describe("project hosting schemas", () => {
4
4
  it("accepts the canonical published branch route", () => {
5
5
  expect(ProjectHostingSchema.parse({ publishedBranchId: "branch-live" })).toEqual({ publishedBranchId: "branch-live" });
@@ -42,6 +42,16 @@ describe("guided onboarding project settings", () => {
42
42
  },
43
43
  ],
44
44
  initialPromptId: "prompt-1",
45
+ issuedAt: "2026-09-09T12:00:00.000Z",
46
+ decisions: [
47
+ {
48
+ questionId: "authentication",
49
+ ask: false,
50
+ llmOptionId: "required",
51
+ confidence: 0.99,
52
+ guessOptionId: "required",
53
+ },
54
+ ],
45
55
  };
46
56
  it("parses a snapshotted questionnaire session", () => {
47
57
  expect(ShapeSessionSchema.parse(shapeSession)).toEqual(shapeSession);
@@ -55,6 +65,16 @@ describe("guided onboarding project settings", () => {
55
65
  catalogVersion: "v1",
56
66
  })).toThrow();
57
67
  });
68
+ it("parses a project-level initial prompt", () => {
69
+ const initialPrompt = {
70
+ id: "prompt-1",
71
+ projectId: "project-1",
72
+ prompt: "Build an expense tracker",
73
+ createdBy: "user-1",
74
+ createdAt: 1788952800000,
75
+ };
76
+ expect(InitialPromptSchema.parse(initialPrompt)).toEqual(initialPrompt);
77
+ });
58
78
  it("does not allow clients to write a shape session", () => {
59
79
  expect(ProjectSettingsExternalSchema.parse({ shapeSession })).toEqual({});
60
80
  expect(ProjectTemplateOverridesSchema.parse({ settings: { shapeSession } })).toEqual({ settings: {} });
@@ -136,3 +156,22 @@ describe("getAgentNativeRootPackageJsonWriteError", () => {
136
156
  })).toBeUndefined();
137
157
  });
138
158
  });
159
+ describe("getProjectState", () => {
160
+ const project = (fields) => fields;
161
+ it("prefers the state field", () => {
162
+ expect(getProjectState(project({ state: "archived" }))).toBe("archived");
163
+ expect(isProjectDeleted(project({ state: "deleted" }))).toBe(true);
164
+ });
165
+ it("falls back to the legacy deleted field", () => {
166
+ expect(getProjectState(project({ deleted: true }))).toBe("deleted");
167
+ expect(isProjectDeleted(project({ deleted: true }))).toBe(true);
168
+ });
169
+ /** A restored project keeps `deleted: true` but gets an explicit state. */
170
+ it("lets an explicit state win over the legacy field", () => {
171
+ expect(isProjectDeleted(project({ state: "active", deleted: true }))).toBe(false);
172
+ });
173
+ it("defaults to active", () => {
174
+ expect(getProjectState(project({}))).toBe("active");
175
+ expect(isProjectDeleted(project({ deleted: false }))).toBe(false);
176
+ });
177
+ });
@@ -1,5 +1,18 @@
1
1
  import type { GenerateCompletionStep } from "./codegen.js";
2
2
  import { z } from "zod";
3
+ /**
4
+ * Client-safe metadata for a Publish Agent skill: enough for a picker UI
5
+ * (id, name, description), without the instruction `content` sent to the
6
+ * model. `id` is the shared key the server uses to attach that content —
7
+ * see `PUBLISH_AGENT_SKILLS` in `packages/service/publish-agent/config.ts`.
8
+ */
9
+ export declare const PublishAgentSkillInfoSchema: z.ZodObject<{
10
+ id: z.ZodString;
11
+ name: z.ZodString;
12
+ description: z.ZodString;
13
+ }, z.core.$strip>;
14
+ export type PublishAgentSkillInfo = z.infer<typeof PublishAgentSkillInfoSchema>;
15
+ export declare const PUBLISH_AGENT_SKILLS_INFO: readonly PublishAgentSkillInfo[];
3
16
  export declare const PublishAgentSessionCursorSchema: z.ZodObject<{
4
17
  updatedAt: z.ZodNumber;
5
18
  id: z.ZodString;
@@ -1,4 +1,24 @@
1
1
  import { z } from "zod";
2
+ /**
3
+ * Client-safe metadata for a Publish Agent skill: enough for a picker UI
4
+ * (id, name, description), without the instruction `content` sent to the
5
+ * model. `id` is the shared key the server uses to attach that content —
6
+ * see `PUBLISH_AGENT_SKILLS` in `packages/service/publish-agent/config.ts`.
7
+ */
8
+ export const PublishAgentSkillInfoSchema = z
9
+ .object({
10
+ id: z.string(),
11
+ name: z.string(),
12
+ description: z.string(),
13
+ })
14
+ .meta({ title: "PublishAgentSkillInfo" });
15
+ export const PUBLISH_AGENT_SKILLS_INFO = [
16
+ {
17
+ id: "publish-agent-skill-create-page",
18
+ name: "create-page",
19
+ description: "Create a new page in the Builder CMS space. Use when the user asks to create, add, or scaffold a new page.",
20
+ },
21
+ ];
2
22
  export const PublishAgentSessionCursorSchema = z
3
23
  .object({
4
24
  updatedAt: z.number().int(),
package/src/realtime.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { z } from "zod";
2
- export declare const BUILDER_REALTIME_MODEL: "gpt-realtime-2.1";
2
+ export declare const BUILDER_REALTIME_MODEL: "gpt-live-1";
3
+ export declare const BUILDER_REALTIME_LEGACY_MODEL: "gpt-realtime-2.1";
4
+ export declare const BUILDER_REALTIME_DELEGATED_MODEL: "gpt-5.6-luna";
3
5
  export declare const BUILDER_REALTIME_MAX_SDP_LENGTH = 256000;
4
6
  export declare const BUILDER_REALTIME_MAX_SESSION_BYTES = 64000;
5
7
  export declare const OpenAIRealtimeFunctionToolSchema: z.ZodObject<{
@@ -137,9 +139,122 @@ export declare const OpenAIRealtimeSessionConfigSchema: z.ZodObject<{
137
139
  include: z.ZodOptional<z.ZodArray<z.ZodString>>;
138
140
  }, z.core.$strip>;
139
141
  export type OpenAIRealtimeSessionConfig = z.infer<typeof OpenAIRealtimeSessionConfigSchema>;
142
+ /** The subset of GPT-Live session configuration accepted by Builder Connect. */
143
+ export declare const OpenAILiveSessionConfigSchema: z.ZodObject<{
144
+ model: z.ZodLiteral<"gpt-live-1">;
145
+ instructions: z.ZodOptional<z.ZodString>;
146
+ audio: z.ZodOptional<z.ZodObject<{
147
+ output: z.ZodOptional<z.ZodObject<{
148
+ voice: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
149
+ id: z.ZodString;
150
+ }, z.core.$strip>]>>;
151
+ }, z.core.$strip>>;
152
+ }, z.core.$strip>>;
153
+ delegation: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodObject<{
154
+ type: z.ZodLiteral<"responses">;
155
+ responses: z.ZodObject<{
156
+ model: z.ZodLiteral<"gpt-5.6-luna">;
157
+ instructions: z.ZodOptional<z.ZodString>;
158
+ tools: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
159
+ type: z.ZodLiteral<"function">;
160
+ name: z.ZodString;
161
+ description: z.ZodOptional<z.ZodString>;
162
+ parameters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
163
+ strict: z.ZodOptional<z.ZodBoolean>;
164
+ }, z.core.$strip>, z.ZodObject<{
165
+ type: z.ZodLiteral<"web_search">;
166
+ }, z.core.$strip>]>>>;
167
+ tool_choice: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
168
+ auto: "auto";
169
+ none: "none";
170
+ required: "required";
171
+ }>, z.ZodObject<{
172
+ type: z.ZodLiteral<"function">;
173
+ name: z.ZodString;
174
+ }, z.core.$strip>]>>;
175
+ parallel_tool_calls: z.ZodOptional<z.ZodBoolean>;
176
+ max_output_tokens: z.ZodOptional<z.ZodNumber>;
177
+ service_tier: z.ZodOptional<z.ZodEnum<{
178
+ auto: "auto";
179
+ default: "default";
180
+ priority: "priority";
181
+ }>>;
182
+ reasoning: z.ZodOptional<z.ZodObject<{
183
+ effort: z.ZodEnum<{
184
+ high: "high";
185
+ low: "low";
186
+ max: "max";
187
+ medium: "medium";
188
+ minimal: "minimal";
189
+ none: "none";
190
+ xhigh: "xhigh";
191
+ }>;
192
+ }, z.core.$strip>>;
193
+ }, z.core.$strip>;
194
+ }, z.core.$strip>, z.ZodObject<{
195
+ type: z.ZodLiteral<"client">;
196
+ }, z.core.$strip>]>>>;
197
+ store: z.ZodOptional<z.ZodBoolean>;
198
+ }, z.core.$strip>;
199
+ export type OpenAILiveSessionConfig = z.infer<typeof OpenAILiveSessionConfigSchema>;
140
200
  export declare const BuilderRealtimeSessionRequestSchema: z.ZodObject<{
141
201
  sdp: z.ZodString;
142
- session: z.ZodObject<{
202
+ session: z.ZodUnion<readonly [z.ZodObject<{
203
+ model: z.ZodLiteral<"gpt-live-1">;
204
+ instructions: z.ZodOptional<z.ZodString>;
205
+ audio: z.ZodOptional<z.ZodObject<{
206
+ output: z.ZodOptional<z.ZodObject<{
207
+ voice: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
208
+ id: z.ZodString;
209
+ }, z.core.$strip>]>>;
210
+ }, z.core.$strip>>;
211
+ }, z.core.$strip>>;
212
+ delegation: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodObject<{
213
+ type: z.ZodLiteral<"responses">;
214
+ responses: z.ZodObject<{
215
+ model: z.ZodLiteral<"gpt-5.6-luna">;
216
+ instructions: z.ZodOptional<z.ZodString>;
217
+ tools: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
218
+ type: z.ZodLiteral<"function">;
219
+ name: z.ZodString;
220
+ description: z.ZodOptional<z.ZodString>;
221
+ parameters: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
222
+ strict: z.ZodOptional<z.ZodBoolean>;
223
+ }, z.core.$strip>, z.ZodObject<{
224
+ type: z.ZodLiteral<"web_search">;
225
+ }, z.core.$strip>]>>>;
226
+ tool_choice: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
227
+ auto: "auto";
228
+ none: "none";
229
+ required: "required";
230
+ }>, z.ZodObject<{
231
+ type: z.ZodLiteral<"function">;
232
+ name: z.ZodString;
233
+ }, z.core.$strip>]>>;
234
+ parallel_tool_calls: z.ZodOptional<z.ZodBoolean>;
235
+ max_output_tokens: z.ZodOptional<z.ZodNumber>;
236
+ service_tier: z.ZodOptional<z.ZodEnum<{
237
+ auto: "auto";
238
+ default: "default";
239
+ priority: "priority";
240
+ }>>;
241
+ reasoning: z.ZodOptional<z.ZodObject<{
242
+ effort: z.ZodEnum<{
243
+ high: "high";
244
+ low: "low";
245
+ max: "max";
246
+ medium: "medium";
247
+ minimal: "minimal";
248
+ none: "none";
249
+ xhigh: "xhigh";
250
+ }>;
251
+ }, z.core.$strip>>;
252
+ }, z.core.$strip>;
253
+ }, z.core.$strip>, z.ZodObject<{
254
+ type: z.ZodLiteral<"client">;
255
+ }, z.core.$strip>]>>>;
256
+ store: z.ZodOptional<z.ZodBoolean>;
257
+ }, z.core.$strip>, z.ZodObject<{
143
258
  type: z.ZodLiteral<"realtime">;
144
259
  model: z.ZodLiteral<"gpt-realtime-2.1">;
145
260
  output_modalities: z.ZodTuple<[z.ZodLiteral<"audio">], null>;
@@ -227,6 +342,6 @@ export declare const BuilderRealtimeSessionRequestSchema: z.ZodObject<{
227
342
  disabled: "disabled";
228
343
  }>, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
229
344
  include: z.ZodOptional<z.ZodArray<z.ZodString>>;
230
- }, z.core.$strip>;
345
+ }, z.core.$strip>]>;
231
346
  }, z.core.$strip>;
232
347
  export type BuilderRealtimeSessionRequest = z.infer<typeof BuilderRealtimeSessionRequestSchema>;
package/src/realtime.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { z } from "zod";
2
- export const BUILDER_REALTIME_MODEL = "gpt-realtime-2.1";
2
+ export const BUILDER_REALTIME_MODEL = "gpt-live-1";
3
+ export const BUILDER_REALTIME_LEGACY_MODEL = "gpt-realtime-2.1";
4
+ export const BUILDER_REALTIME_DELEGATED_MODEL = "gpt-5.6-luna";
3
5
  export const BUILDER_REALTIME_MAX_SDP_LENGTH = 256000;
4
6
  export const BUILDER_REALTIME_MAX_SESSION_BYTES = 64000;
5
7
  const utf8ByteLength = (value) => new TextEncoder().encode(value).length;
@@ -109,7 +111,7 @@ const OpenAIRealtimeToolChoiceSchema = z.union([
109
111
  export const OpenAIRealtimeSessionConfigSchema = z
110
112
  .object({
111
113
  type: z.literal("realtime"),
112
- model: z.literal(BUILDER_REALTIME_MODEL),
114
+ model: z.literal(BUILDER_REALTIME_LEGACY_MODEL),
113
115
  output_modalities: z.tuple([z.literal("audio")]),
114
116
  instructions: z.string().max(32000).optional(),
115
117
  audio: z
@@ -148,11 +150,76 @@ export const OpenAIRealtimeSessionConfigSchema = z
148
150
  })
149
151
  .refine((session) => utf8ByteLength(JSON.stringify(session)) <=
150
152
  BUILDER_REALTIME_MAX_SESSION_BYTES, { message: "Realtime session configuration is too large" });
153
+ const OpenAILiveToolSchema = z.union([
154
+ OpenAIRealtimeFunctionToolSchema,
155
+ z.object({ type: z.literal("web_search") }),
156
+ ]);
157
+ const OpenAILiveResponsesDelegationSchema = z.object({
158
+ model: z.literal(BUILDER_REALTIME_DELEGATED_MODEL),
159
+ instructions: z.string().max(32000).optional(),
160
+ tools: z.array(OpenAILiveToolSchema).max(32).optional(),
161
+ tool_choice: z
162
+ .union([
163
+ z.enum(["auto", "none", "required"]),
164
+ z.object({
165
+ type: z.literal("function"),
166
+ name: z.string().min(1).max(64),
167
+ }),
168
+ ])
169
+ .optional(),
170
+ parallel_tool_calls: z.boolean().optional(),
171
+ max_output_tokens: z.number().int().min(16).max(4096).optional(),
172
+ service_tier: z.enum(["auto", "default", "priority"]).optional(),
173
+ reasoning: z
174
+ .object({
175
+ effort: z.enum([
176
+ "none",
177
+ "minimal",
178
+ "low",
179
+ "medium",
180
+ "high",
181
+ "xhigh",
182
+ "max",
183
+ ]),
184
+ })
185
+ .optional(),
186
+ });
187
+ const OpenAILiveDelegationSchema = z.union([
188
+ z.object({
189
+ type: z.literal("responses"),
190
+ responses: OpenAILiveResponsesDelegationSchema,
191
+ }),
192
+ z.object({ type: z.literal("client") }),
193
+ ]);
194
+ const OpenAILiveOutputAudioSchema = z.object({
195
+ voice: z
196
+ .union([
197
+ z.string().min(1).max(128),
198
+ z.object({ id: z.string().min(1).max(256) }),
199
+ ])
200
+ .optional(),
201
+ });
202
+ /** The subset of GPT-Live session configuration accepted by Builder Connect. */
203
+ export const OpenAILiveSessionConfigSchema = z
204
+ .object({
205
+ model: z.literal(BUILDER_REALTIME_MODEL),
206
+ instructions: z.string().max(32000).optional(),
207
+ audio: z
208
+ .object({ output: OpenAILiveOutputAudioSchema.optional() })
209
+ .optional(),
210
+ delegation: OpenAILiveDelegationSchema.nullable().optional(),
211
+ store: z.boolean().optional(),
212
+ })
213
+ .refine((session) => utf8ByteLength(JSON.stringify(session)) <=
214
+ BUILDER_REALTIME_MAX_SESSION_BYTES, { message: "Live session configuration is too large" });
151
215
  export const BuilderRealtimeSessionRequestSchema = z.object({
152
216
  sdp: z
153
217
  .string()
154
218
  .min(1)
155
219
  .max(BUILDER_REALTIME_MAX_SDP_LENGTH)
156
220
  .refine((sdp) => sdp.trim().length > 0, { message: "SDP cannot be blank" }),
157
- session: OpenAIRealtimeSessionConfigSchema,
221
+ session: z.union([
222
+ OpenAILiveSessionConfigSchema,
223
+ OpenAIRealtimeSessionConfigSchema,
224
+ ]),
158
225
  });
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { BUILDER_REALTIME_MAX_SDP_LENGTH, BuilderRealtimeSessionRequestSchema, OpenAIRealtimeSessionConfigSchema, } from "./realtime.js";
2
+ import { BUILDER_REALTIME_MAX_SDP_LENGTH, BuilderRealtimeSessionRequestSchema, OpenAILiveSessionConfigSchema, OpenAIRealtimeSessionConfigSchema, } from "./realtime.js";
3
3
  const validSession = {
4
4
  type: "realtime",
5
5
  model: "gpt-realtime-2.1",
@@ -7,6 +7,38 @@ const validSession = {
7
7
  instructions: "Help the user operate their Builder app.",
8
8
  };
9
9
  describe("BuilderRealtimeSessionRequestSchema", () => {
10
+ it("accepts a GPT-Live WebRTC session with Responses delegation", () => {
11
+ const result = BuilderRealtimeSessionRequestSchema.parse({
12
+ sdp: "v=0\r\no=- 1 1 IN IP4 127.0.0.1",
13
+ session: {
14
+ model: "gpt-live-1",
15
+ instructions: "Help the user operate their Builder app.",
16
+ audio: { output: { voice: "marin" } },
17
+ delegation: {
18
+ type: "responses",
19
+ responses: {
20
+ model: "gpt-5.6-luna",
21
+ tools: [
22
+ {
23
+ type: "function",
24
+ name: "navigate",
25
+ parameters: { type: "object", properties: {} },
26
+ },
27
+ { type: "web_search" },
28
+ ],
29
+ reasoning: { effort: "minimal" },
30
+ },
31
+ },
32
+ },
33
+ });
34
+ expect(result.session).toMatchObject({
35
+ model: "gpt-live-1",
36
+ delegation: {
37
+ type: "responses",
38
+ responses: { model: "gpt-5.6-luna" },
39
+ },
40
+ });
41
+ });
10
42
  it("accepts a bounded audio session with tools", () => {
11
43
  var _a;
12
44
  const result = BuilderRealtimeSessionRequestSchema.parse({
@@ -39,10 +71,13 @@ describe("BuilderRealtimeSessionRequestSchema", () => {
39
71
  },
40
72
  });
41
73
  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
- });
74
+ expect("tools" in result.session).toBe(true);
75
+ if ("tools" in result.session) {
76
+ expect((_a = result.session.tools) === null || _a === void 0 ? void 0 : _a[0]).toMatchObject({
77
+ type: "function",
78
+ name: "navigate",
79
+ });
80
+ }
46
81
  });
47
82
  it("strips unknown request, session, and known nested config keys", () => {
48
83
  const sdp = " v=0\r\n";
@@ -86,6 +121,19 @@ describe("BuilderRealtimeSessionRequestSchema", () => {
86
121
  },
87
122
  }).success).toBe(false);
88
123
  });
124
+ it("rejects unsupported GPT-Live delegation types", () => {
125
+ expect(OpenAILiveSessionConfigSchema.safeParse({
126
+ model: "gpt-live-1",
127
+ delegation: { type: "unknown" },
128
+ }).success).toBe(false);
129
+ expect(OpenAILiveSessionConfigSchema.safeParse({
130
+ model: "gpt-live-1",
131
+ delegation: {
132
+ type: "responses",
133
+ responses: { model: "gpt-5.6" },
134
+ },
135
+ }).success).toBe(false);
136
+ });
89
137
  it("rejects empty and oversized SDP offers", () => {
90
138
  expect(BuilderRealtimeSessionRequestSchema.safeParse({
91
139
  sdp: " ",