@builder.io/ai-utils 0.109.1 → 0.111.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.109.1",
3
+ "version": "0.111.0",
4
4
  "description": "Builder.io AI utils",
5
5
  "files": [
6
6
  "src"
package/src/codegen.d.ts CHANGED
@@ -61,6 +61,7 @@ export declare const CustomInstructionSchema: z.ZodObject<{
61
61
  name: z.ZodString;
62
62
  description: z.ZodOptional<z.ZodString>;
63
63
  }, z.core.$strip>>>;
64
+ contentOnClient: z.ZodOptional<z.ZodBoolean>;
64
65
  }, z.core.$strip>;
65
66
  export type CustomInstruction = z.infer<typeof CustomInstructionSchema>;
66
67
  /** Reasoning effort level for LLM completions. */
@@ -2723,6 +2724,7 @@ export declare const CodeGenInputOptionsSchema: z.ZodObject<{
2723
2724
  name: z.ZodString;
2724
2725
  description: z.ZodOptional<z.ZodString>;
2725
2726
  }, z.core.$strip>>>;
2727
+ contentOnClient: z.ZodOptional<z.ZodBoolean>;
2726
2728
  }, z.core.$strip>>>;
2727
2729
  customAgents: z.ZodOptional<z.ZodArray<z.ZodObject<{
2728
2730
  name: z.ZodString;
@@ -4099,6 +4101,10 @@ export interface CodegenFeedback {
4099
4101
  id: string;
4100
4102
  feedbackText?: string;
4101
4103
  feedbackSentiment?: string;
4104
+ /** 1–5 star rating. Only the first-generation feedback card sends it. */
4105
+ feedbackRating?: number;
4106
+ /** Set by UIs whose feedback needs handling beyond storage. */
4107
+ feedbackSource?: "first-generation-card";
4102
4108
  framework?: string;
4103
4109
  acceptedLines?: number;
4104
4110
  beforeCommit?: GitSnapshot;
@@ -4424,6 +4430,8 @@ export interface WorkspaceFolder {
4424
4430
  * Optional, so older CLIs/configs that ignore it stay compatible.
4425
4431
  */
4426
4432
  templateRepoUrl?: string;
4433
+ /** Local-cluster testing only: branch or tag of `templateRepoUrl` to clone. Absent means its default branch. */
4434
+ templateRef?: string;
4427
4435
  repoName?: string;
4428
4436
  resetGit?: boolean;
4429
4437
  /**
package/src/codegen.js CHANGED
@@ -63,6 +63,12 @@ export const CustomInstructionSchema = z
63
63
  * built-in skills (e.g. `code-review`).
64
64
  */
65
65
  ownedAgents: z.array(CustomAgentInfoSchema).optional(),
66
+ /**
67
+ * The CLI kept this skill's body locally and sent `content` empty, so the
68
+ * service forwards `Skill` calls for it to the CLI instead of answering
69
+ * them. Clients that predate this flag send the full body.
70
+ */
71
+ contentOnClient: z.boolean().optional(),
66
72
  })
67
73
  .meta({ title: "CustomInstruction" });
68
74
  /** Reasoning effort level for LLM completions. */
@@ -1257,10 +1263,10 @@ export const VerifySetupCommandToolInputSchema = z
1257
1263
  dependencies: z
1258
1264
  .array(z.object({
1259
1265
  tool: z.string().meta({
1260
- description: "The tool/binary to verify (e.g., 'node', 'npm', 'git')",
1266
+ description: "The tool to install with mise (e.g., 'node', 'pnpm', 'python')",
1261
1267
  }),
1262
1268
  version: z.string().optional().meta({
1263
- description: "Optional version requirement (e.g., '>=18.0.0', '20.x')",
1269
+ description: "Optional mise version: an exact version ('22.11.0'), a prefix ('22', '3.11'), 'latest' or 'lts'. Not a semver range: convert package.json engines like '22.x' or '>=20' to '22' / '20'.",
1264
1270
  }),
1265
1271
  }))
1266
1272
  .optional(),
package/src/projects.d.ts CHANGED
@@ -1331,6 +1331,7 @@ export declare const ProjectSettingsSchema: z.ZodObject<{
1331
1331
  name: z.ZodString;
1332
1332
  description: z.ZodOptional<z.ZodString>;
1333
1333
  }, z.core.$strip>>>;
1334
+ contentOnClient: z.ZodOptional<z.ZodBoolean>;
1334
1335
  }, z.core.$strip>>>;
1335
1336
  customAgents: z.ZodOptional<z.ZodArray<z.ZodObject<{
1336
1337
  name: z.ZodString;
@@ -1673,6 +1674,7 @@ export declare const ProjectSettingsExternalSchema: z.ZodObject<{
1673
1674
  name: z.ZodString;
1674
1675
  description: z.ZodOptional<z.ZodString>;
1675
1676
  }, z.core.$strip>>>;
1677
+ contentOnClient: z.ZodOptional<z.ZodBoolean>;
1676
1678
  }, z.core.$strip>>>;
1677
1679
  customAgents: z.ZodOptional<z.ZodArray<z.ZodObject<{
1678
1680
  name: z.ZodString;
@@ -1963,6 +1965,8 @@ export interface Project {
1963
1965
  templateId?: string | null;
1964
1966
  /** @internal Read-only source repo cloned to bootstrap the project. Immutable. Absent for non-template projects. Distinct from `repoUrl`, which is always the real push remote. */
1965
1967
  templateRepoUrl?: string;
1968
+ /** @internal Local-cluster testing only: the branch or tag of `templateRepoUrl` to clone instead of its default branch. Ignored once the real remote is seeded. */
1969
+ templateRef?: string;
1966
1970
  /** Local filesystem path for desktop-app projects. */
1967
1971
  localPath?: string | null;
1968
1972
  /** The state of the project. Use `isProjectDeleted()` helper for backwards-compatible checks. */
@@ -2252,6 +2256,8 @@ export interface CreateProjectOptions {
2252
2256
  useInternalHost?: boolean;
2253
2257
  /** @internal Read-only source repo cloned to bootstrap the project. Carried through to Firestore. */
2254
2258
  templateRepoUrl?: string;
2259
+ /** @internal Local-cluster testing only: branch or tag of `templateRepoUrl` to clone. Carried through to Firestore. */
2260
+ templateRef?: string;
2255
2261
  /** @internal First-class hosting config copied from the template. Carried through to Firestore. */
2256
2262
  hosting?: ProjectHosting;
2257
2263
  /** @internal When true, provision the internalHost repo eagerly at creation. Hardcoded true for v0. */
@@ -2275,6 +2281,11 @@ export declare const GetProjectsQuerySchema: z.ZodObject<{
2275
2281
  limit: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
2276
2282
  }, z.core.$strip>;
2277
2283
  export type GetProjectsQuery = z.infer<typeof GetProjectsQuerySchema>;
2284
+ /**
2285
+ * A plain branch or tag name. It becomes an argument to `git clone -b`, so
2286
+ * anything that could be read as an option or a path escape is rejected.
2287
+ */
2288
+ export declare const TemplateRefSchema: z.ZodString;
2278
2289
  /**
2279
2290
  * Request body for `POST /projects/create`. Identity/credentials come from the
2280
2291
  * authenticated request context, never the body.
@@ -2290,6 +2301,7 @@ export declare const CreateProjectRequestSchema: z.ZodObject<{
2290
2301
  name: z.ZodString;
2291
2302
  initialMessage: z.ZodOptional<z.ZodString>;
2292
2303
  designSystems: z.ZodOptional<z.ZodArray<z.ZodString>>;
2304
+ templateRef: z.ZodOptional<z.ZodString>;
2293
2305
  }, z.core.$strip>;
2294
2306
  export type CreateProjectRequest = z.infer<typeof CreateProjectRequestSchema>;
2295
2307
  export interface BuilderMyContext {
@@ -2902,6 +2914,7 @@ export declare const UpdateProjectOptionsSchema: z.ZodObject<{
2902
2914
  name: z.ZodString;
2903
2915
  description: z.ZodOptional<z.ZodString>;
2904
2916
  }, z.core.$strip>>>;
2917
+ contentOnClient: z.ZodOptional<z.ZodBoolean>;
2905
2918
  }, z.core.$strip>>>>;
2906
2919
  customAgents: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodObject<{
2907
2920
  name: z.ZodString;
@@ -4064,6 +4077,397 @@ export declare const EnsureFusionConfigParamsSchema: z.ZodObject<{
4064
4077
  }>>;
4065
4078
  }, z.core.$strip>;
4066
4079
  export type EnsureFusionConfigParams = z.infer<typeof EnsureFusionConfigParamsSchema>;
4080
+ /** Request body for `POST /projects/pr-message`. */
4081
+ export declare const PRMessageRequestSchema: z.ZodObject<{
4082
+ projectId: z.ZodString;
4083
+ prNumber: z.ZodNumber;
4084
+ message: z.ZodOptional<z.ZodNullable<z.ZodObject<{
4085
+ idempotencyKey: z.ZodOptional<z.ZodString>;
4086
+ user: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
4087
+ role: z.ZodEnum<{
4088
+ agent: "agent";
4089
+ user: "user";
4090
+ }>;
4091
+ principals: z.ZodOptional<z.ZodArray<z.ZodString>>;
4092
+ jobs: z.ZodOptional<z.ZodArray<z.ZodString>>;
4093
+ permissions: z.ZodOptional<z.ZodArray<z.ZodEnum<{
4094
+ createBranches: "createBranches";
4095
+ editCode: "editCode";
4096
+ modifyMcpServers: "modifyMcpServers";
4097
+ modifyProjectSettings: "modifyProjectSettings";
4098
+ sendPullRequests: "sendPullRequests";
4099
+ view: "view";
4100
+ }>>>;
4101
+ channelId: z.ZodOptional<z.ZodString>;
4102
+ source: z.ZodEnum<{
4103
+ azure: "azure";
4104
+ bitbucket: "bitbucket";
4105
+ github: "github";
4106
+ gitlab: "gitlab";
4107
+ jira: "jira";
4108
+ linear: "linear";
4109
+ slack: "slack";
4110
+ teams: "teams";
4111
+ telegram: "telegram";
4112
+ whatsapp: "whatsapp";
4113
+ }>;
4114
+ userId: z.ZodOptional<z.ZodString>;
4115
+ userName: z.ZodOptional<z.ZodString>;
4116
+ userEmail: z.ZodOptional<z.ZodString>;
4117
+ photoURL: z.ZodOptional<z.ZodString>;
4118
+ builderUserId: z.ZodOptional<z.ZodString>;
4119
+ link: z.ZodOptional<z.ZodString>;
4120
+ }, z.core.$strip>, z.ZodObject<{
4121
+ role: z.ZodEnum<{
4122
+ agent: "agent";
4123
+ user: "user";
4124
+ }>;
4125
+ principals: z.ZodOptional<z.ZodArray<z.ZodString>>;
4126
+ jobs: z.ZodOptional<z.ZodArray<z.ZodString>>;
4127
+ permissions: z.ZodOptional<z.ZodArray<z.ZodEnum<{
4128
+ createBranches: "createBranches";
4129
+ editCode: "editCode";
4130
+ modifyMcpServers: "modifyMcpServers";
4131
+ modifyProjectSettings: "modifyProjectSettings";
4132
+ sendPullRequests: "sendPullRequests";
4133
+ view: "view";
4134
+ }>>>;
4135
+ channelId: z.ZodOptional<z.ZodString>;
4136
+ source: z.ZodLiteral<"builder.io">;
4137
+ userId: z.ZodOptional<z.ZodString>;
4138
+ userName: z.ZodOptional<z.ZodString>;
4139
+ userEmail: z.ZodOptional<z.ZodString>;
4140
+ photoURL: z.ZodOptional<z.ZodString>;
4141
+ }, z.core.$strip>, z.ZodObject<{
4142
+ role: z.ZodEnum<{
4143
+ agent: "agent";
4144
+ user: "user";
4145
+ }>;
4146
+ principals: z.ZodOptional<z.ZodArray<z.ZodString>>;
4147
+ jobs: z.ZodOptional<z.ZodArray<z.ZodString>>;
4148
+ permissions: z.ZodOptional<z.ZodArray<z.ZodEnum<{
4149
+ createBranches: "createBranches";
4150
+ editCode: "editCode";
4151
+ modifyMcpServers: "modifyMcpServers";
4152
+ modifyProjectSettings: "modifyProjectSettings";
4153
+ sendPullRequests: "sendPullRequests";
4154
+ view: "view";
4155
+ }>>>;
4156
+ channelId: z.ZodOptional<z.ZodString>;
4157
+ source: z.ZodLiteral<"agent">;
4158
+ userId: z.ZodOptional<z.ZodString>;
4159
+ userName: z.ZodOptional<z.ZodString>;
4160
+ userEmail: z.ZodOptional<z.ZodString>;
4161
+ }, z.core.$strip>], "source">>;
4162
+ uiContextPrompt: z.ZodOptional<z.ZodString>;
4163
+ ephemeralUserPrompt: z.ZodOptional<z.ZodString>;
4164
+ displayPrompt: z.ZodOptional<z.ZodString>;
4165
+ files: z.ZodOptional<z.ZodArray<z.ZodString>>;
4166
+ systemReminders: z.ZodOptional<z.ZodArray<z.ZodObject<{
4167
+ text: z.ZodString;
4168
+ tag: z.ZodOptional<z.ZodString>;
4169
+ ephemeral: z.ZodOptional<z.ZodBoolean>;
4170
+ }, z.core.$strip>>>;
4171
+ includeBaseFiles: z.ZodOptional<z.ZodBoolean>;
4172
+ attachments: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
4173
+ type: z.ZodLiteral<"upload">;
4174
+ contentType: z.ZodEnum<{
4175
+ "application/json": "application/json";
4176
+ "application/pdf": "application/pdf";
4177
+ "image/gif": "image/gif";
4178
+ "image/jpeg": "image/jpeg";
4179
+ "image/png": "image/png";
4180
+ "image/webp": "image/webp";
4181
+ "text/plain": "text/plain";
4182
+ }>;
4183
+ name: z.ZodString;
4184
+ dataUrl: z.ZodString;
4185
+ text: z.ZodOptional<z.ZodString>;
4186
+ size: z.ZodNumber;
4187
+ id: z.ZodString;
4188
+ originalUrl: z.ZodOptional<z.ZodString>;
4189
+ ephemeral: z.ZodOptional<z.ZodBoolean>;
4190
+ }, z.core.$strip>, z.ZodObject<{
4191
+ type: z.ZodLiteral<"template">;
4192
+ name: z.ZodString;
4193
+ id: z.ZodNumber;
4194
+ }, z.core.$strip>, z.ZodObject<{
4195
+ type: z.ZodLiteral<"url">;
4196
+ value: z.ZodString;
4197
+ }, z.core.$strip>, z.ZodObject<{
4198
+ type: z.ZodLiteral<"builder">;
4199
+ html: z.ZodString;
4200
+ }, z.core.$strip>, z.ZodObject<{
4201
+ type: z.ZodLiteral<"figma">;
4202
+ html: z.ZodString;
4203
+ }, z.core.$strip>], "type">>>;
4204
+ logsCheckpoint: z.ZodOptional<z.ZodBoolean>;
4205
+ dropAbortedPrompt: z.ZodOptional<z.ZodBoolean>;
4206
+ runValidateCommand: z.ZodOptional<z.ZodBoolean>;
4207
+ modelOverride: z.ZodOptional<z.ZodString>;
4208
+ agentModelOverrides: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>>;
4209
+ softContextWindow: z.ZodOptional<z.ZodNumber>;
4210
+ maxCompletions: z.ZodOptional<z.ZodNumber>;
4211
+ isManualContinue: z.ZodOptional<z.ZodBoolean>;
4212
+ includeRelevantMemories: z.ZodOptional<z.ZodBoolean>;
4213
+ category: z.ZodOptional<z.ZodUnion<readonly [z.ZodTemplateLiteral<`repair-${string}`>, z.ZodLiteral<"user-normal">, z.ZodLiteral<"user-figma">, z.ZodLiteral<"user-pdf">, z.ZodLiteral<"user-image">, z.ZodLiteral<"setup-agent">, z.ZodTemplateLiteral<`background-${string}`>, z.ZodTemplateLiteral<`indexing-${string}`>]>>;
4214
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
4215
+ autoPush: z.ZodOptional<z.ZodEnum<{
4216
+ "ff-push": "ff-push";
4217
+ "force-push": "force-push";
4218
+ "merge-push": "merge-push";
4219
+ none: "none";
4220
+ "safe-push": "safe-push";
4221
+ }>>;
4222
+ syncChanges: z.ZodOptional<z.ZodObject<{
4223
+ remoteBranches: z.ZodOptional<z.ZodEnum<{
4224
+ ai: "ai";
4225
+ both: "both";
4226
+ main: "main";
4227
+ }>>;
4228
+ fastForward: z.ZodOptional<z.ZodEnum<{
4229
+ auto: "auto";
4230
+ never: "never";
4231
+ required: "required";
4232
+ }>>;
4233
+ canPush: z.ZodOptional<z.ZodBoolean>;
4234
+ uncommittedChanges: z.ZodOptional<z.ZodEnum<{
4235
+ commit: "commit";
4236
+ fail: "fail";
4237
+ stash: "stash";
4238
+ }>>;
4239
+ requestRefresh: z.ZodOptional<z.ZodBoolean>;
4240
+ allowUnrelatedHistory: z.ZodOptional<z.ZodBoolean>;
4241
+ resetToBase: z.ZodOptional<z.ZodBoolean>;
4242
+ updateLastCommits: z.ZodOptional<z.ZodBoolean>;
4243
+ }, z.core.$strip>>;
4244
+ enabledMCPs: z.ZodOptional<z.ZodArray<z.ZodString>>;
4245
+ skippedMCPs: z.ZodOptional<z.ZodArray<z.ZodString>>;
4246
+ sessionMode: z.ZodOptional<z.ZodEnum<{
4247
+ "auto-planning": "auto-planning";
4248
+ "deep-research": "deep-research";
4249
+ normal: "normal";
4250
+ planning: "planning";
4251
+ }>>;
4252
+ queue: z.ZodOptional<z.ZodBoolean>;
4253
+ enableQAReviewTool: z.ZodOptional<z.ZodBoolean>;
4254
+ reasoning: z.ZodOptional<z.ZodEnum<{
4255
+ auto: "auto";
4256
+ high: "high";
4257
+ low: "low";
4258
+ max: "max";
4259
+ medium: "medium";
4260
+ minimal: "minimal";
4261
+ none: "none";
4262
+ xhigh: "xhigh";
4263
+ }>>;
4264
+ repair: z.ZodOptional<z.ZodBoolean>;
4265
+ forceCompact: z.ZodOptional<z.ZodBoolean>;
4266
+ userPrompt: z.ZodString;
4267
+ }, z.core.$strip>>>;
4268
+ }, z.core.$strip>;
4269
+ export type PRMessageRequest = z.infer<typeof PRMessageRequestSchema>;
4270
+ /** Max length of `userMessage.userPrompt` on `POST /agents/run`. */
4271
+ export declare const AGENTS_RUN_MAX_USER_PROMPT_LENGTH = 300000;
4272
+ /** Request body for `POST /agents/run` (public Background Agents API). */
4273
+ export declare const AgentsRunRequestSchema: z.ZodObject<{
4274
+ userMessage: z.ZodObject<{
4275
+ idempotencyKey: z.ZodOptional<z.ZodString>;
4276
+ user: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
4277
+ role: z.ZodEnum<{
4278
+ agent: "agent";
4279
+ user: "user";
4280
+ }>;
4281
+ principals: z.ZodOptional<z.ZodArray<z.ZodString>>;
4282
+ jobs: z.ZodOptional<z.ZodArray<z.ZodString>>;
4283
+ permissions: z.ZodOptional<z.ZodArray<z.ZodEnum<{
4284
+ createBranches: "createBranches";
4285
+ editCode: "editCode";
4286
+ modifyMcpServers: "modifyMcpServers";
4287
+ modifyProjectSettings: "modifyProjectSettings";
4288
+ sendPullRequests: "sendPullRequests";
4289
+ view: "view";
4290
+ }>>>;
4291
+ channelId: z.ZodOptional<z.ZodString>;
4292
+ source: z.ZodEnum<{
4293
+ azure: "azure";
4294
+ bitbucket: "bitbucket";
4295
+ github: "github";
4296
+ gitlab: "gitlab";
4297
+ jira: "jira";
4298
+ linear: "linear";
4299
+ slack: "slack";
4300
+ teams: "teams";
4301
+ telegram: "telegram";
4302
+ whatsapp: "whatsapp";
4303
+ }>;
4304
+ userId: z.ZodOptional<z.ZodString>;
4305
+ userName: z.ZodOptional<z.ZodString>;
4306
+ userEmail: z.ZodOptional<z.ZodString>;
4307
+ photoURL: z.ZodOptional<z.ZodString>;
4308
+ builderUserId: z.ZodOptional<z.ZodString>;
4309
+ link: z.ZodOptional<z.ZodString>;
4310
+ }, z.core.$strip>, z.ZodObject<{
4311
+ role: z.ZodEnum<{
4312
+ agent: "agent";
4313
+ user: "user";
4314
+ }>;
4315
+ principals: z.ZodOptional<z.ZodArray<z.ZodString>>;
4316
+ jobs: z.ZodOptional<z.ZodArray<z.ZodString>>;
4317
+ permissions: z.ZodOptional<z.ZodArray<z.ZodEnum<{
4318
+ createBranches: "createBranches";
4319
+ editCode: "editCode";
4320
+ modifyMcpServers: "modifyMcpServers";
4321
+ modifyProjectSettings: "modifyProjectSettings";
4322
+ sendPullRequests: "sendPullRequests";
4323
+ view: "view";
4324
+ }>>>;
4325
+ channelId: z.ZodOptional<z.ZodString>;
4326
+ source: z.ZodLiteral<"builder.io">;
4327
+ userId: z.ZodOptional<z.ZodString>;
4328
+ userName: z.ZodOptional<z.ZodString>;
4329
+ userEmail: z.ZodOptional<z.ZodString>;
4330
+ photoURL: z.ZodOptional<z.ZodString>;
4331
+ }, z.core.$strip>, z.ZodObject<{
4332
+ role: z.ZodEnum<{
4333
+ agent: "agent";
4334
+ user: "user";
4335
+ }>;
4336
+ principals: z.ZodOptional<z.ZodArray<z.ZodString>>;
4337
+ jobs: z.ZodOptional<z.ZodArray<z.ZodString>>;
4338
+ permissions: z.ZodOptional<z.ZodArray<z.ZodEnum<{
4339
+ createBranches: "createBranches";
4340
+ editCode: "editCode";
4341
+ modifyMcpServers: "modifyMcpServers";
4342
+ modifyProjectSettings: "modifyProjectSettings";
4343
+ sendPullRequests: "sendPullRequests";
4344
+ view: "view";
4345
+ }>>>;
4346
+ channelId: z.ZodOptional<z.ZodString>;
4347
+ source: z.ZodLiteral<"agent">;
4348
+ userId: z.ZodOptional<z.ZodString>;
4349
+ userName: z.ZodOptional<z.ZodString>;
4350
+ userEmail: z.ZodOptional<z.ZodString>;
4351
+ }, z.core.$strip>], "source">>;
4352
+ uiContextPrompt: z.ZodOptional<z.ZodString>;
4353
+ ephemeralUserPrompt: z.ZodOptional<z.ZodString>;
4354
+ displayPrompt: z.ZodOptional<z.ZodString>;
4355
+ files: z.ZodOptional<z.ZodArray<z.ZodString>>;
4356
+ systemReminders: z.ZodOptional<z.ZodArray<z.ZodObject<{
4357
+ text: z.ZodString;
4358
+ tag: z.ZodOptional<z.ZodString>;
4359
+ ephemeral: z.ZodOptional<z.ZodBoolean>;
4360
+ }, z.core.$strip>>>;
4361
+ includeBaseFiles: z.ZodOptional<z.ZodBoolean>;
4362
+ attachments: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
4363
+ type: z.ZodLiteral<"upload">;
4364
+ contentType: z.ZodEnum<{
4365
+ "application/json": "application/json";
4366
+ "application/pdf": "application/pdf";
4367
+ "image/gif": "image/gif";
4368
+ "image/jpeg": "image/jpeg";
4369
+ "image/png": "image/png";
4370
+ "image/webp": "image/webp";
4371
+ "text/plain": "text/plain";
4372
+ }>;
4373
+ name: z.ZodString;
4374
+ dataUrl: z.ZodString;
4375
+ text: z.ZodOptional<z.ZodString>;
4376
+ size: z.ZodNumber;
4377
+ id: z.ZodString;
4378
+ originalUrl: z.ZodOptional<z.ZodString>;
4379
+ ephemeral: z.ZodOptional<z.ZodBoolean>;
4380
+ }, z.core.$strip>, z.ZodObject<{
4381
+ type: z.ZodLiteral<"template">;
4382
+ name: z.ZodString;
4383
+ id: z.ZodNumber;
4384
+ }, z.core.$strip>, z.ZodObject<{
4385
+ type: z.ZodLiteral<"url">;
4386
+ value: z.ZodString;
4387
+ }, z.core.$strip>, z.ZodObject<{
4388
+ type: z.ZodLiteral<"builder">;
4389
+ html: z.ZodString;
4390
+ }, z.core.$strip>, z.ZodObject<{
4391
+ type: z.ZodLiteral<"figma">;
4392
+ html: z.ZodString;
4393
+ }, z.core.$strip>], "type">>>;
4394
+ logsCheckpoint: z.ZodOptional<z.ZodBoolean>;
4395
+ dropAbortedPrompt: z.ZodOptional<z.ZodBoolean>;
4396
+ runValidateCommand: z.ZodOptional<z.ZodBoolean>;
4397
+ modelOverride: z.ZodOptional<z.ZodString>;
4398
+ agentModelOverrides: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>>;
4399
+ softContextWindow: z.ZodOptional<z.ZodNumber>;
4400
+ maxCompletions: z.ZodOptional<z.ZodNumber>;
4401
+ isManualContinue: z.ZodOptional<z.ZodBoolean>;
4402
+ includeRelevantMemories: z.ZodOptional<z.ZodBoolean>;
4403
+ category: z.ZodOptional<z.ZodUnion<readonly [z.ZodTemplateLiteral<`repair-${string}`>, z.ZodLiteral<"user-normal">, z.ZodLiteral<"user-figma">, z.ZodLiteral<"user-pdf">, z.ZodLiteral<"user-image">, z.ZodLiteral<"setup-agent">, z.ZodTemplateLiteral<`background-${string}`>, z.ZodTemplateLiteral<`indexing-${string}`>]>>;
4404
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
4405
+ autoPush: z.ZodOptional<z.ZodEnum<{
4406
+ "ff-push": "ff-push";
4407
+ "force-push": "force-push";
4408
+ "merge-push": "merge-push";
4409
+ none: "none";
4410
+ "safe-push": "safe-push";
4411
+ }>>;
4412
+ syncChanges: z.ZodOptional<z.ZodObject<{
4413
+ remoteBranches: z.ZodOptional<z.ZodEnum<{
4414
+ ai: "ai";
4415
+ both: "both";
4416
+ main: "main";
4417
+ }>>;
4418
+ fastForward: z.ZodOptional<z.ZodEnum<{
4419
+ auto: "auto";
4420
+ never: "never";
4421
+ required: "required";
4422
+ }>>;
4423
+ canPush: z.ZodOptional<z.ZodBoolean>;
4424
+ uncommittedChanges: z.ZodOptional<z.ZodEnum<{
4425
+ commit: "commit";
4426
+ fail: "fail";
4427
+ stash: "stash";
4428
+ }>>;
4429
+ requestRefresh: z.ZodOptional<z.ZodBoolean>;
4430
+ allowUnrelatedHistory: z.ZodOptional<z.ZodBoolean>;
4431
+ resetToBase: z.ZodOptional<z.ZodBoolean>;
4432
+ updateLastCommits: z.ZodOptional<z.ZodBoolean>;
4433
+ }, z.core.$strip>>;
4434
+ enabledMCPs: z.ZodOptional<z.ZodArray<z.ZodString>>;
4435
+ skippedMCPs: z.ZodOptional<z.ZodArray<z.ZodString>>;
4436
+ sessionMode: z.ZodOptional<z.ZodEnum<{
4437
+ "auto-planning": "auto-planning";
4438
+ "deep-research": "deep-research";
4439
+ normal: "normal";
4440
+ planning: "planning";
4441
+ }>>;
4442
+ queue: z.ZodOptional<z.ZodBoolean>;
4443
+ enableQAReviewTool: z.ZodOptional<z.ZodBoolean>;
4444
+ reasoning: z.ZodOptional<z.ZodEnum<{
4445
+ auto: "auto";
4446
+ high: "high";
4447
+ low: "low";
4448
+ max: "max";
4449
+ medium: "medium";
4450
+ minimal: "minimal";
4451
+ none: "none";
4452
+ xhigh: "xhigh";
4453
+ }>>;
4454
+ repair: z.ZodOptional<z.ZodBoolean>;
4455
+ forceCompact: z.ZodOptional<z.ZodBoolean>;
4456
+ userPrompt: z.ZodString;
4457
+ }, z.core.$strip>;
4458
+ userId: z.ZodOptional<z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>>;
4459
+ userEmail: z.ZodOptional<z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>>;
4460
+ branchName: z.ZodOptional<z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>>;
4461
+ projectId: z.ZodOptional<z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>>;
4462
+ context: z.ZodOptional<z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>, z.ZodTransform<Record<string, unknown> | undefined, Record<string, unknown> | null | undefined>>>;
4463
+ }, z.core.$strip>;
4464
+ export type AgentsRunRequest = z.infer<typeof AgentsRunRequestSchema>;
4465
+ /** Request body for `PUT /agents/webhook-secret`. */
4466
+ export declare const UpdateWebhookSecretRequestSchema: z.ZodObject<{
4467
+ webhookId: z.ZodString;
4468
+ secret: z.ZodOptional<z.ZodString>;
4469
+ }, z.core.$strip>;
4470
+ export type UpdateWebhookSecretRequest = z.infer<typeof UpdateWebhookSecretRequestSchema>;
4067
4471
  /**
4068
4472
  * `settings.environmentVariables` is stored with **plaintext** secret values;
4069
4473
  * secrets are masked before this shape leaves ai-services, same as
@@ -4248,6 +4652,7 @@ export declare const ProjectTemplateSchema: z.ZodObject<{
4248
4652
  name: z.ZodString;
4249
4653
  description: z.ZodOptional<z.ZodString>;
4250
4654
  }, z.core.$strip>>>;
4655
+ contentOnClient: z.ZodOptional<z.ZodBoolean>;
4251
4656
  }, z.core.$strip>>>>;
4252
4657
  customAgents: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodObject<{
4253
4658
  name: z.ZodString;
@@ -4678,6 +5083,7 @@ export declare const ProjectTemplateOverridesSchema: z.ZodObject<{
4678
5083
  name: z.ZodString;
4679
5084
  description: z.ZodOptional<z.ZodString>;
4680
5085
  }, z.core.$strip>>>;
5086
+ contentOnClient: z.ZodOptional<z.ZodBoolean>;
4681
5087
  }, z.core.$strip>>>>;
4682
5088
  customAgents: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodObject<{
4683
5089
  name: z.ZodString;
@@ -5072,6 +5478,7 @@ export declare const CreateProjectTemplateFromProjectOptionsSchema: z.ZodPipe<z.
5072
5478
  name: z.ZodString;
5073
5479
  description: z.ZodOptional<z.ZodString>;
5074
5480
  }, z.core.$strip>>>;
5481
+ contentOnClient: z.ZodOptional<z.ZodBoolean>;
5075
5482
  }, z.core.$strip>>>>;
5076
5483
  customAgents: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodObject<{
5077
5484
  name: z.ZodString;
@@ -5449,6 +5856,7 @@ export declare const CreateProjectTemplateFromProjectOptionsSchema: z.ZodPipe<z.
5449
5856
  name: z.ZodString;
5450
5857
  description: z.ZodOptional<z.ZodString>;
5451
5858
  }, z.core.$strip>>>;
5859
+ contentOnClient: z.ZodOptional<z.ZodBoolean>;
5452
5860
  }, z.core.$strip>>>>;
5453
5861
  customAgents: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodObject<{
5454
5862
  name: z.ZodString;
@@ -5795,6 +6203,7 @@ export declare const CreateProjectTemplateFromProjectOptionsSchema: z.ZodPipe<z.
5795
6203
  name: string;
5796
6204
  description?: string | undefined;
5797
6205
  }[] | undefined;
6206
+ contentOnClient?: boolean | undefined;
5798
6207
  }[] | undefined;
5799
6208
  customAgents?: {
5800
6209
  name: string;
@@ -6064,6 +6473,7 @@ export declare const CreateProjectTemplateFromProjectOptionsSchema: z.ZodPipe<z.
6064
6473
  name: string;
6065
6474
  description?: string | undefined;
6066
6475
  }[] | undefined;
6476
+ contentOnClient?: boolean | undefined;
6067
6477
  }[] | undefined;
6068
6478
  customAgents?: {
6069
6479
  name: string;
@@ -6327,6 +6737,7 @@ export declare const CreateProjectTemplateFromProjectOptionsSchema: z.ZodPipe<z.
6327
6737
  name: string;
6328
6738
  description?: string | undefined;
6329
6739
  }[] | undefined;
6740
+ contentOnClient?: boolean | undefined;
6330
6741
  }[] | undefined;
6331
6742
  customAgents?: {
6332
6743
  name: string;
@@ -6674,6 +7085,7 @@ export declare const ListProjectTemplatesResponseSchema: z.ZodObject<{
6674
7085
  name: z.ZodString;
6675
7086
  description: z.ZodOptional<z.ZodString>;
6676
7087
  }, z.core.$strip>>>;
7088
+ contentOnClient: z.ZodOptional<z.ZodBoolean>;
6677
7089
  }, z.core.$strip>>>>;
6678
7090
  customAgents: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodObject<{
6679
7091
  name: z.ZodString;
@@ -7099,6 +7511,7 @@ export declare const UpdateProjectTemplateOptionsSchema: z.ZodObject<{
7099
7511
  name: z.ZodString;
7100
7512
  description: z.ZodOptional<z.ZodString>;
7101
7513
  }, z.core.$strip>>>;
7514
+ contentOnClient: z.ZodOptional<z.ZodBoolean>;
7102
7515
  }, z.core.$strip>>>>;
7103
7516
  customAgents: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodObject<{
7104
7517
  name: z.ZodString;
@@ -7508,6 +7921,7 @@ export declare const UpdateProjectTemplateResponseSchema: z.ZodObject<{
7508
7921
  name: z.ZodString;
7509
7922
  description: z.ZodOptional<z.ZodString>;
7510
7923
  }, z.core.$strip>>>;
7924
+ contentOnClient: z.ZodOptional<z.ZodBoolean>;
7511
7925
  }, z.core.$strip>>>>;
7512
7926
  customAgents: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodObject<{
7513
7927
  name: z.ZodString;
package/src/projects.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { EnvironmentVariableSchema, SetupDependencySchema, } from "./common-schemas";
3
- import { AutoPushModeSchema, CustomInstructionSchema, CustomAgentDefinitionSchema, ExitStateSchema, FileOverrideSchema, } from "./codegen";
3
+ import { AutoPushModeSchema, CustomInstructionSchema, CustomAgentDefinitionSchema, ExitStateSchema, FileOverrideSchema, GenerateUserMessageSchema, } from "./codegen";
4
4
  /**
5
5
  * Detect git provider from a repository URL (HTTPS or SSH).
6
6
  * Used by both error diagnostics and connectivity checks for consistent provider labeling.
@@ -1107,6 +1107,20 @@ export const GetProjectsQuerySchema = z.object({
1107
1107
  /** At most this many projects, counted after access, hidden and DSI filtering. */
1108
1108
  limit: z.coerce.number().int().min(1).max(1000).optional(),
1109
1109
  });
1110
+ /**
1111
+ * A plain branch or tag name. It becomes an argument to `git clone -b`, so
1112
+ * anything that could be read as an option or a path escape is rejected.
1113
+ */
1114
+ export const TemplateRefSchema = z
1115
+ .string()
1116
+ .max(255)
1117
+ .regex(/^[A-Za-z0-9][A-Za-z0-9._/-]*$/)
1118
+ .refine((ref) => !ref.includes(".."), {
1119
+ message: "templateRef must not contain '..'",
1120
+ })
1121
+ .meta({
1122
+ description: "Local-cluster testing only: branch or tag of the template repo to clone. Rejected unless the service runs against a local cluster.",
1123
+ });
1110
1124
  /**
1111
1125
  * Request body for `POST /projects/create`. Identity/credentials come from the
1112
1126
  * authenticated request context, never the body.
@@ -1118,6 +1132,8 @@ export const CreateProjectRequestSchema = z.object({
1118
1132
  initialMessage: z.string().optional(),
1119
1133
  /** Design system ids to seed into project settings at creation. */
1120
1134
  designSystems: z.array(z.string()).optional(),
1135
+ /** Local-cluster testing only: branch or tag of the template repo to clone. Template source only. */
1136
+ templateRef: TemplateRefSchema.optional(),
1121
1137
  });
1122
1138
  export function resolveBranchIsPublic(isPublic, defaultBranchType) {
1123
1139
  if (isPublic !== undefined)
@@ -2343,6 +2359,49 @@ export const EnsureFusionConfigParamsSchema = z.object({
2343
2359
  description: "Fusion environment type override. When not provided, the environment is resolved from branch/project settings.",
2344
2360
  }),
2345
2361
  });
2362
+ // `agentType` swaps the agent config (tools, model, system prompt) for a
2363
+ // message. HTTP callers have no need to pick an agent, so strip it.
2364
+ const HttpUserMessageSchema = GenerateUserMessageSchema.omit({
2365
+ agentType: true,
2366
+ });
2367
+ /** Request body for `POST /projects/pr-message`. */
2368
+ export const PRMessageRequestSchema = z.object({
2369
+ projectId: z.string().min(1).meta({ description: "Builder project ID" }),
2370
+ prNumber: z.number().int().positive().meta({ description: "PR number" }),
2371
+ message: HttpUserMessageSchema.extend({ userPrompt: z.string().min(1) })
2372
+ .nullish()
2373
+ .meta({
2374
+ description: "The user message to send to the agent (prompt, attachments, etc.). If omitted or null, the request resolves the PR branch and returns without starting a container or notifying the agent.",
2375
+ }),
2376
+ });
2377
+ /** Max length of `userMessage.userPrompt` on `POST /agents/run`. */
2378
+ export const AGENTS_RUN_MAX_USER_PROMPT_LENGTH = 300000;
2379
+ // Public API clients often send `null` for unset fields, which /agents/run has
2380
+ // always treated as absent. Accept it and normalize to `undefined`.
2381
+ const nullishToUndefined = (schema) => schema
2382
+ .nullish()
2383
+ .transform((v) => v !== null && v !== void 0 ? v : undefined)
2384
+ .optional();
2385
+ /** Request body for `POST /agents/run` (public Background Agents API). */
2386
+ export const AgentsRunRequestSchema = z.object({
2387
+ userMessage: HttpUserMessageSchema.extend({
2388
+ userPrompt: z.string().min(1).max(AGENTS_RUN_MAX_USER_PROMPT_LENGTH),
2389
+ }),
2390
+ userId: nullishToUndefined(z.string()),
2391
+ userEmail: nullishToUndefined(z.string()),
2392
+ branchName: nullishToUndefined(z.string()),
2393
+ projectId: nullishToUndefined(z.string()),
2394
+ context: nullishToUndefined(z.record(z.string(), z.unknown())).meta({
2395
+ description: "Arbitrary key-value context passed to webhook payloads.",
2396
+ }),
2397
+ });
2398
+ /** Request body for `PUT /agents/webhook-secret`. */
2399
+ export const UpdateWebhookSecretRequestSchema = z.object({
2400
+ webhookId: z.string().min(1),
2401
+ secret: z.string().optional().meta({
2402
+ description: "An empty or omitted secret removes it.",
2403
+ }),
2404
+ });
2346
2405
  /**
2347
2406
  * `settings.environmentVariables` is stored with **plaintext** secret values;
2348
2407
  * secrets are masked before this shape leaves ai-services, same as
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import { DeleteBranchOptionsSchema, EnsureFusionConfigParamsSchema, } from "./projects.js";
2
+ import { AGENTS_RUN_MAX_USER_PROMPT_LENGTH, AgentsRunRequestSchema, DeleteBranchOptionsSchema, EnsureFusionConfigParamsSchema, PRMessageRequestSchema, UpdateWebhookSecretRequestSchema, } from "./projects.js";
3
3
  describe("DeleteBranchOptionsSchema", () => {
4
4
  const ids = { projectId: "p1", branchName: "main" };
5
5
  it("defaults hard to false", () => {
@@ -38,3 +38,92 @@ describe("EnsureFusionConfigParamsSchema", () => {
38
38
  }).success).toBe(false);
39
39
  });
40
40
  });
41
+ describe("PRMessageRequestSchema", () => {
42
+ const ids = { projectId: "p1", prNumber: 42 };
43
+ it("accepts an omitted or null message", () => {
44
+ expect(PRMessageRequestSchema.safeParse(ids).success).toBe(true);
45
+ expect(PRMessageRequestSchema.safeParse({ ...ids, message: null }).success).toBe(true);
46
+ });
47
+ it("requires a non-empty userPrompt when a message is given", () => {
48
+ expect(PRMessageRequestSchema.safeParse({ ...ids, message: { userPrompt: "" } })
49
+ .success).toBe(false);
50
+ expect(PRMessageRequestSchema.safeParse({ ...ids, message: {} }).success).toBe(false);
51
+ });
52
+ it("requires a positive integer prNumber", () => {
53
+ for (const prNumber of [0, -1, 1.5, "42"]) {
54
+ expect(PRMessageRequestSchema.safeParse({ ...ids, prNumber }).success).toBe(false);
55
+ }
56
+ });
57
+ it("strips message keys GenerateUserMessage doesn't declare", () => {
58
+ const parsed = PRMessageRequestSchema.parse({
59
+ ...ids,
60
+ message: { userPrompt: "fix it", vcpImportId: "v1" },
61
+ });
62
+ expect(parsed.message).toEqual({ userPrompt: "fix it" });
63
+ });
64
+ it("strips agentType from the message", () => {
65
+ const parsed = PRMessageRequestSchema.parse({
66
+ ...ids,
67
+ message: { userPrompt: "fix it", agentType: "project-configuration" },
68
+ });
69
+ expect(parsed.message).toEqual({ userPrompt: "fix it" });
70
+ });
71
+ });
72
+ describe("AgentsRunRequestSchema", () => {
73
+ it("requires a non-empty userPrompt", () => {
74
+ expect(AgentsRunRequestSchema.safeParse({}).success).toBe(false);
75
+ expect(AgentsRunRequestSchema.safeParse({ userMessage: { userPrompt: "" } })
76
+ .success).toBe(false);
77
+ });
78
+ it("caps userPrompt length", () => {
79
+ const at = "a".repeat(AGENTS_RUN_MAX_USER_PROMPT_LENGTH);
80
+ expect(AgentsRunRequestSchema.safeParse({ userMessage: { userPrompt: at } })
81
+ .success).toBe(true);
82
+ expect(AgentsRunRequestSchema.safeParse({
83
+ userMessage: { userPrompt: at + "a" },
84
+ }).success).toBe(false);
85
+ });
86
+ it("strips agentType from userMessage", () => {
87
+ const parsed = AgentsRunRequestSchema.parse({
88
+ userMessage: { userPrompt: "x", agentType: "project-configuration" },
89
+ });
90
+ expect(parsed.userMessage).toEqual({ userPrompt: "x" });
91
+ });
92
+ it("treats null optional fields as absent", () => {
93
+ const parsed = AgentsRunRequestSchema.parse({
94
+ userMessage: { userPrompt: "x" },
95
+ userId: null,
96
+ userEmail: "a@b.c",
97
+ branchName: null,
98
+ projectId: null,
99
+ context: null,
100
+ });
101
+ expect(parsed).toEqual({
102
+ userMessage: { userPrompt: "x" },
103
+ userId: undefined,
104
+ userEmail: "a@b.c",
105
+ branchName: undefined,
106
+ projectId: undefined,
107
+ context: undefined,
108
+ });
109
+ });
110
+ });
111
+ describe("AgentsRunRequest type", () => {
112
+ it("keeps optional fields optional on the parsed type", () => {
113
+ // Compile-time check: fails to type-check if the null normalization makes
114
+ // optional keys required on the output type.
115
+ const minimal = { userMessage: { userPrompt: "x" } };
116
+ expect(AgentsRunRequestSchema.parse(minimal)).toEqual(minimal);
117
+ });
118
+ });
119
+ describe("UpdateWebhookSecretRequestSchema", () => {
120
+ it("requires a non-empty webhookId", () => {
121
+ expect(UpdateWebhookSecretRequestSchema.safeParse({}).success).toBe(false);
122
+ expect(UpdateWebhookSecretRequestSchema.safeParse({ webhookId: "" }).success).toBe(false);
123
+ });
124
+ it("accepts an empty or omitted secret (removal)", () => {
125
+ expect(UpdateWebhookSecretRequestSchema.safeParse({ webhookId: "w", secret: "" })
126
+ .success).toBe(true);
127
+ expect(UpdateWebhookSecretRequestSchema.safeParse({ webhookId: "w" }).success).toBe(true);
128
+ });
129
+ });
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { AGENT_NATIVE_STARTER_REPO, getAgentNativeRootPackageJsonWriteError, getManagedDatabasePackageJsonError, getProjectState, GetShapeQuestionnaireOptionsSchema, InitialPromptSchema, isProjectDeleted, matchesAgentNativeStarter, findFigmaFileUrl, mentionsFigmaFileUrl, parseFigmaFileUrl, ProjectHostingExternalSchema, ProjectHostingSchema, ProjectSettingsExternalSchema, ProjectTemplateOverridesSchema, ShapeSessionSchema, SubmitShapeAnswersOptionsSchema, } from "./projects";
2
+ import { AGENT_NATIVE_STARTER_REPO, CreateProjectRequestSchema, TemplateRefSchema, getAgentNativeRootPackageJsonWriteError, getManagedDatabasePackageJsonError, getProjectState, GetShapeQuestionnaireOptionsSchema, InitialPromptSchema, isProjectDeleted, matchesAgentNativeStarter, findFigmaFileUrl, mentionsFigmaFileUrl, parseFigmaFileUrl, ProjectHostingExternalSchema, ProjectHostingSchema, ProjectSettingsExternalSchema, ProjectTemplateOverridesSchema, ShapeSessionSchema, SubmitShapeAnswersOptionsSchema, } 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" });
@@ -294,3 +294,37 @@ describe("getProjectState", () => {
294
294
  expect(isProjectDeleted(project({ deleted: false }))).toBe(false);
295
295
  });
296
296
  });
297
+ describe("TemplateRefSchema", () => {
298
+ it.each(["main", "eval/kyle/faster-start", "v1.2.3", "release_2026.10"])("accepts the branch or tag name %s", (ref) => {
299
+ expect(TemplateRefSchema.safeParse(ref).success).toBe(true);
300
+ });
301
+ it.each([
302
+ "",
303
+ "-b",
304
+ "--upload-pack=evil",
305
+ "../escape",
306
+ "a/../b",
307
+ "has space",
308
+ "semi;colon",
309
+ "a".repeat(256),
310
+ ])("rejects %j", (ref) => {
311
+ expect(TemplateRefSchema.safeParse(ref).success).toBe(false);
312
+ });
313
+ });
314
+ describe("CreateProjectRequestSchema templateRef", () => {
315
+ const base = {
316
+ source: { kind: "template", templateId: "agent-native-starter" },
317
+ name: "p",
318
+ };
319
+ it("is optional", () => {
320
+ expect(CreateProjectRequestSchema.parse(base).templateRef).toBeUndefined();
321
+ });
322
+ it("carries a valid ref through", () => {
323
+ expect(CreateProjectRequestSchema.parse({ ...base, templateRef: "eval/x" })
324
+ .templateRef).toBe("eval/x");
325
+ });
326
+ it("rejects an invalid ref", () => {
327
+ expect(CreateProjectRequestSchema.safeParse({ ...base, templateRef: "--x" })
328
+ .success).toBe(false);
329
+ });
330
+ });