@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/package.json +1 -1
- package/src/codegen.d.ts +62 -22
- package/src/codegen.js +23 -16
- package/src/events.d.ts +27 -2
- package/src/events.js +8 -0
- package/src/projects.d.ts +1246 -8
- package/src/projects.js +92 -11
- package/src/projects.test.js +40 -1
- package/src/publish-agent.d.ts +13 -0
- package/src/publish-agent.js +20 -0
- package/src/realtime.d.ts +118 -3
- package/src/realtime.js +70 -3
- package/src/realtime.spec.js +53 -5
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
|
-
|
|
865
|
-
|
|
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
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
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
|
-
|
|
2131
|
-
|
|
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.
|
package/src/projects.test.js
CHANGED
|
@@ -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
|
+
});
|
package/src/publish-agent.d.ts
CHANGED
|
@@ -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;
|
package/src/publish-agent.js
CHANGED
|
@@ -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-
|
|
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-
|
|
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(
|
|
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:
|
|
221
|
+
session: z.union([
|
|
222
|
+
OpenAILiveSessionConfigSchema,
|
|
223
|
+
OpenAIRealtimeSessionConfigSchema,
|
|
224
|
+
]),
|
|
158
225
|
});
|
package/src/realtime.spec.js
CHANGED
|
@@ -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(
|
|
43
|
-
|
|
44
|
-
|
|
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: " ",
|