@usepipr/sdk 0.1.3 → 0.2.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/dist/index.mjs CHANGED
@@ -1,2 +1,629 @@
1
- import { a as schema, c as parseReviewResult, d as reviewResultSchema, f as reviewSchemaExample, i as jsonSchema, l as parseReviewSummary, m as md, n as definePipr, o as schemas, p as reviewSummarySchema, r as definePlugin, s as parseReviewFinding, t as z, u as reviewFindingSchema } from "./src-CR3NktDT.mjs";
1
+ import { i as assertSupportedCommandRestCapture, n as builtinReadOnlyToolBrand, r as configFactoryBrand, t as renderPromptValue } from "./prompt-render-BWiLG-qu.mjs";
2
+ import { z, z as z$1 } from "zod";
3
+ //#region src/prompt.ts
4
+ /** Creates trimmed Markdown from a template literal with common indentation removed. */
5
+ function md(strings, ...values) {
6
+ let text = "";
7
+ for (let index = 0; index < strings.length; index += 1) {
8
+ text += strings[index] ?? "";
9
+ if (index < values.length) text += String(values[index] ?? "");
10
+ }
11
+ return stripCommonIndent(text).trim();
12
+ }
13
+ /** Removes common leading indentation from multiline text. */
14
+ function stripCommonIndent(value) {
15
+ const lines = value.replaceAll(" ", " ").split(/\r?\n/);
16
+ const nonEmpty = lines.filter((line) => line.trim().length > 0);
17
+ const indent = Math.min(...nonEmpty.map((line) => line.match(/^ */)?.[0].length ?? 0));
18
+ return lines.map((line) => line.slice(indent)).join("\n");
19
+ }
20
+ //#endregion
21
+ //#region src/review-contract.ts
22
+ const nonEmptyStringSchema = z$1.string().min(1);
23
+ const positiveIntegerSchema = z$1.number().int().positive();
24
+ /** Zod schema for a review summary. */
25
+ const reviewSummarySchema = z$1.strictObject({
26
+ title: nonEmptyStringSchema.optional(),
27
+ body: nonEmptyStringSchema
28
+ });
29
+ /** Zod schema for one inline review finding. */
30
+ const reviewFindingSchema = z$1.strictObject({
31
+ body: nonEmptyStringSchema,
32
+ path: nonEmptyStringSchema,
33
+ rangeId: nonEmptyStringSchema,
34
+ side: z$1.enum(["RIGHT", "LEFT"]),
35
+ startLine: positiveIntegerSchema,
36
+ endLine: positiveIntegerSchema,
37
+ suggestedFix: nonEmptyStringSchema.optional()
38
+ });
39
+ /** Zod schema for pipr's core pull request review result. */
40
+ const reviewResultSchema = z$1.strictObject({
41
+ summary: reviewSummarySchema,
42
+ inlineFindings: z$1.array(reviewFindingSchema)
43
+ });
44
+ /** Parses model output for pipr's main pull request review schema. */
45
+ function parseReviewResult(value) {
46
+ return reviewResultSchema.parse(value);
47
+ }
48
+ /** Parses a review summary value. */
49
+ function parseReviewSummary(value) {
50
+ return reviewSummarySchema.parse(value);
51
+ }
52
+ /** Parses one inline review finding. */
53
+ function parseReviewFinding(value) {
54
+ return reviewFindingSchema.parse(value);
55
+ }
56
+ /** Returns a small valid example for the main pull request review schema. */
57
+ function reviewSchemaExample() {
58
+ return {
59
+ summary: {
60
+ title: "Optional concise review title.",
61
+ body: "Concise pull request review summary."
62
+ },
63
+ inlineFindings: [{
64
+ body: "Specific issue and why it matters.",
65
+ path: "src/example.ts",
66
+ rangeId: "rng_example",
67
+ side: "RIGHT",
68
+ startLine: 1,
69
+ endLine: 1,
70
+ suggestedFix: "return safeValue;"
71
+ }]
72
+ };
73
+ }
74
+ //#endregion
75
+ //#region src/schema.ts
76
+ const coreReviewOutputSchemaId = "core/pr-review";
77
+ /** Defines a typed schema from a Zod schema. */
78
+ function schema(definition) {
79
+ if (!definition || typeof definition.id !== "string") throw new Error("pipr.schema requires { id, schema }");
80
+ assertUserSchemaId(definition.id);
81
+ return createZodSchema(definition.id, definition.schema);
82
+ }
83
+ /** Defines a typed schema from JSON Schema. The generic type is caller supplied. */
84
+ function jsonSchema(definition) {
85
+ if (!definition || typeof definition.id !== "string") throw new Error("pipr.jsonSchema requires { id, schema }");
86
+ assertUserSchemaId(definition.id);
87
+ const zodSchema = z$1.fromJSONSchema(definition.schema);
88
+ return createSchema(definition.id, (value) => zodSchema.parse(value), definition.schema);
89
+ }
90
+ /** Built-in schemas available as reusable agent output contracts. */
91
+ const schemas = {
92
+ review: createZodSchema(coreReviewOutputSchemaId, reviewResultSchema),
93
+ summary: createZodSchema("core/summary", reviewSummarySchema)
94
+ };
95
+ function createSchema(id, parseValue, schemaJson) {
96
+ return {
97
+ kind: "pipr.schema",
98
+ id,
99
+ jsonSchema: schemaJson,
100
+ parse(value) {
101
+ return parseValue(value);
102
+ },
103
+ safeParse(value) {
104
+ try {
105
+ return {
106
+ success: true,
107
+ data: parseValue(value)
108
+ };
109
+ } catch (error) {
110
+ return {
111
+ success: false,
112
+ error: error instanceof Error ? error : new Error(String(error))
113
+ };
114
+ }
115
+ }
116
+ };
117
+ }
118
+ function createZodSchema(id, zodSchema) {
119
+ return createSchema(id, (value) => zodSchema.parse(value), jsonSchemaFromZod(id, zodSchema));
120
+ }
121
+ function assertUserSchemaId(id) {
122
+ if (id.startsWith("core/")) throw new Error(`Schema id '${id}' uses the reserved core/ namespace`);
123
+ }
124
+ function jsonSchemaFromZod(id, schemaDefinition) {
125
+ try {
126
+ return z$1.toJSONSchema(schemaDefinition);
127
+ } catch (error) {
128
+ const detail = error instanceof Error ? error.message : String(error);
129
+ throw new Error(`Schema '${id}' could not be converted to JSON Schema. Use JSON-Schema-representable Zod or pipr.jsonSchema<T>(). ${detail}`);
130
+ }
131
+ }
132
+ //#endregion
133
+ //#region src/builder.ts
134
+ /** Defines a synchronous pipr configuration factory. */
135
+ function definePipr(configure) {
136
+ return {
137
+ kind: "pipr.config-factory",
138
+ [configFactoryBrand]: true,
139
+ build() {
140
+ const builder = createBuilder();
141
+ const result = configure(builder.api);
142
+ if (typeof result === "object" && result !== null && typeof Reflect.get(result, "then") === "function") throw new Error("definePipr configuration callback must be synchronous");
143
+ return builder.plan();
144
+ }
145
+ };
146
+ }
147
+ /** Defines a typed pipr plugin installer. */
148
+ function definePlugin(setup) {
149
+ return { setup };
150
+ }
151
+ function createBuilder() {
152
+ const models = [];
153
+ const agents = [];
154
+ const tasks = [];
155
+ const changeRequestTriggers = [];
156
+ const commands = [];
157
+ const tools = [];
158
+ const publication = {};
159
+ let checks;
160
+ let limits;
161
+ const api = {
162
+ tools: { readOnly: [{
163
+ kind: "pipr.tool",
164
+ name: "readOnly",
165
+ [builtinReadOnlyToolBrand]: true
166
+ }] },
167
+ schemas,
168
+ on: { changeRequest(options) {
169
+ if (!Array.isArray(options.actions) || !options.task) throw new Error("pipr.on.changeRequest requires { actions, task }");
170
+ changeRequestTriggers.push({
171
+ actions: options.actions,
172
+ task: options.task
173
+ });
174
+ } },
175
+ secret(options) {
176
+ if (!options || typeof options.name !== "string") throw new Error("pipr.secret requires { name }");
177
+ if (!/^[A-Z_][A-Z0-9_]*$/.test(options.name)) throw new Error(`Secret '${options.name}' must be an environment variable name`);
178
+ return {
179
+ kind: "pipr.secret",
180
+ name: options.name
181
+ };
182
+ },
183
+ model(options) {
184
+ if (!options || typeof options.provider !== "string" || typeof options.model !== "string") throw new Error("pipr.model requires { provider, model }");
185
+ if (!options.provider || !options.model) throw new Error("pipr.model requires provider and model");
186
+ const profile = {
187
+ kind: "pipr.model",
188
+ id: options.id ?? `${options.provider}/${options.model}`,
189
+ provider: options.provider,
190
+ model: options.model,
191
+ apiKey: options.apiKey,
192
+ options: options.options
193
+ };
194
+ models.push(profile);
195
+ return profile;
196
+ },
197
+ agent(definition) {
198
+ const agent = createAgent(definition);
199
+ agents.push(agent);
200
+ return agent;
201
+ },
202
+ task(definition) {
203
+ if (!definition.name || typeof definition.run !== "function") throw new Error("pipr.task requires { name, run }");
204
+ const task = {
205
+ kind: "pipr.task",
206
+ name: definition.name,
207
+ check: definition.check,
208
+ ...definition.local === false ? { local: false } : {},
209
+ handler: definition.run
210
+ };
211
+ tasks.push(task);
212
+ return task;
213
+ },
214
+ reviewer(options) {
215
+ return createReviewer(api, options);
216
+ },
217
+ review(options) {
218
+ assertKnownReviewRecipeOptions(options);
219
+ registerReviewRecipe(api, options);
220
+ },
221
+ config(options) {
222
+ assertKnownPiprConfigOptions(options);
223
+ mergePublicationConfig(publication, options.publication);
224
+ checks = mergeConfigField("checks", checks, options.checks);
225
+ limits = mergeLimits(limits, options.limits);
226
+ },
227
+ command(options) {
228
+ if (typeof options.pattern !== "string" || !options.task) throw new Error("pipr.command requires { pattern, task }");
229
+ const pattern = options.pattern;
230
+ const tokens = pattern.trim().split(/\s+/).filter(Boolean);
231
+ if (tokens.length === 0) throw new Error("Command pattern must not be empty");
232
+ if (tokens[0] !== "@pipr") throw new Error(`Command pattern '${pattern}' must start with @pipr`);
233
+ assertSupportedCommandRestCapture(pattern);
234
+ commands.push({
235
+ pattern,
236
+ permission: options.permission ?? "write",
237
+ description: options.description,
238
+ parse: options.parse,
239
+ task: options.task
240
+ });
241
+ },
242
+ use(plugin) {
243
+ return plugin.setup(api);
244
+ },
245
+ tool(definition) {
246
+ if (definition.name === "readOnly") throw new Error("Tool name 'readOnly' is reserved for pipr built-in tools");
247
+ const execute = definition.execute;
248
+ let run = definition.run;
249
+ if (!run && !execute) throw new Error(`Tool '${definition.name}' must define run`);
250
+ if (!run) {
251
+ const executeTool = execute;
252
+ if (!executeTool) throw new Error(`Tool '${definition.name}' must define run`);
253
+ run = (options) => executeToolWithRuntimeInput(executeTool, options);
254
+ }
255
+ const tool = {
256
+ kind: "pipr.tool",
257
+ ...definition,
258
+ run
259
+ };
260
+ tools.push(tool);
261
+ return tool;
262
+ },
263
+ schema,
264
+ jsonSchema,
265
+ prompt(strings, ...values) {
266
+ let text = "";
267
+ for (let index = 0; index < strings.length; index += 1) {
268
+ text += strings[index] ?? "";
269
+ if (index < values.length) text += renderPromptValue(values[index]);
270
+ }
271
+ return {
272
+ kind: "pipr.prompt",
273
+ value: stripCommonIndent(text).trim()
274
+ };
275
+ },
276
+ section(title, value) {
277
+ return {
278
+ kind: "pipr.prompt",
279
+ value: `## ${title}\n\n${renderPromptValue(value)}`
280
+ };
281
+ },
282
+ json(value, options) {
283
+ const text = JSON.stringify(value, null, options?.pretty === false ? 0 : 2);
284
+ if (options?.maxCharacters !== void 0 && text.length > options.maxCharacters) throw new Error(`JSON prompt value exceeded ${options.maxCharacters} characters`);
285
+ return {
286
+ kind: "pipr.prompt",
287
+ value: text
288
+ };
289
+ }
290
+ };
291
+ return {
292
+ api,
293
+ plan() {
294
+ assertUnique(tasks.map((task) => task.name), "task");
295
+ assertUnique(commands.map((command) => command.pattern), "command");
296
+ assertModelIdentity(models);
297
+ return {
298
+ models,
299
+ agents,
300
+ tasks,
301
+ changeRequestTriggers,
302
+ commands,
303
+ tools,
304
+ publication,
305
+ checks,
306
+ limits
307
+ };
308
+ }
309
+ };
310
+ }
311
+ function registerReviewRecipe(api, options) {
312
+ const id = options.id;
313
+ registerReviewRecipeEntrypoints(api, createReviewRecipeTask(api, id, options.reviewer ?? createReviewer(api, reviewRecipeReviewerOptions(options, id)), options), options);
314
+ }
315
+ const reviewRecipeOptionKeys = new Set([
316
+ "id",
317
+ "entrypoints",
318
+ "comment",
319
+ "check",
320
+ "timeout",
321
+ "paths",
322
+ "reviewer",
323
+ "name",
324
+ "model",
325
+ "fallbacks",
326
+ "instructions",
327
+ "prompt",
328
+ "tools"
329
+ ]);
330
+ const reviewRecipeEntrypointKeys = new Set(["changeRequest", "command"]);
331
+ const modelProfileConfigSchema = z$1.custom((value) => typeof value === "object" && value !== null && value.kind === "pipr.model" && typeof value.id === "string" && typeof value.provider === "string" && typeof value.model === "string");
332
+ const autoResolveUserRepliesOptionsSchema = z$1.strictObject({
333
+ enabled: z$1.boolean().optional(),
334
+ respondWhenStillValid: z$1.boolean().optional(),
335
+ allowedActors: z$1.enum([
336
+ "author-or-write",
337
+ "write",
338
+ "any"
339
+ ]).optional()
340
+ });
341
+ const autoResolveOptionsSchema = z$1.union([z$1.literal(false), z$1.strictObject({
342
+ enabled: z$1.boolean().optional(),
343
+ model: modelProfileConfigSchema.optional(),
344
+ instructions: z$1.string().min(1).max(4e3).optional(),
345
+ synchronize: z$1.boolean().optional(),
346
+ userReplies: z$1.union([z$1.boolean(), autoResolveUserRepliesOptionsSchema]).optional()
347
+ })]);
348
+ const publicationOptionsSchema = z$1.strictObject({
349
+ maxInlineComments: z$1.number().int().min(0).max(50).optional(),
350
+ autoResolve: autoResolveOptionsSchema.optional()
351
+ });
352
+ const aggregateCheckOptionsSchema = z$1.union([z$1.literal(false), z$1.strictObject({
353
+ enabled: z$1.boolean().optional(),
354
+ name: z$1.string().min(1).optional()
355
+ })]);
356
+ const checksOptionsSchema = z$1.strictObject({ aggregate: aggregateCheckOptionsSchema.optional() });
357
+ const diffManifestLimitsSchema = z$1.strictObject({
358
+ fullMaxBytes: z$1.number().int().positive().optional(),
359
+ fullMaxEstimatedTokens: z$1.number().int().positive().optional(),
360
+ condensedMaxBytes: z$1.number().int().positive().optional(),
361
+ condensedMaxEstimatedTokens: z$1.number().int().positive().optional(),
362
+ toolResponseMaxBytes: z$1.number().int().positive().optional()
363
+ });
364
+ const runtimeLimitsSchema = z$1.strictObject({
365
+ timeoutSeconds: z$1.number().int().positive().max(3600).optional(),
366
+ diffManifest: diffManifestLimitsSchema.optional()
367
+ });
368
+ const piprConfigOptionsSchema = z$1.strictObject({
369
+ publication: publicationOptionsSchema.optional(),
370
+ checks: checksOptionsSchema.optional(),
371
+ limits: runtimeLimitsSchema.optional()
372
+ });
373
+ function assertKnownReviewRecipeOptions(options) {
374
+ const unknownKeys = Object.keys(options).filter((key) => !reviewRecipeOptionKeys.has(key));
375
+ if (unknownKeys.length > 0) throw new Error(`pipr.review received unsupported option fields: ${unknownKeys.join(", ")}.`);
376
+ const entrypoints = options.entrypoints;
377
+ if (entrypoints && typeof entrypoints === "object") {
378
+ const unknownEntrypointKeys = Object.keys(entrypoints).filter((key) => !reviewRecipeEntrypointKeys.has(key));
379
+ if (unknownEntrypointKeys.length > 0) throw new Error(`pipr.review entrypoints received unsupported fields: ${unknownEntrypointKeys.join(", ")}.`);
380
+ }
381
+ }
382
+ function assertKnownPiprConfigOptions(options) {
383
+ const parsed = piprConfigOptionsSchema.safeParse(options);
384
+ if (!parsed.success) throw new Error(formatPiprConfigOptionsError(parsed.error));
385
+ }
386
+ function formatPiprConfigOptionsError(error) {
387
+ const unsupportedFields = firstUnsupportedConfigFields(error.issues, []);
388
+ if (unsupportedFields) return `${piprConfigLabel(unsupportedFields.path)} received unsupported option fields: ${unsupportedFields.keys.join(", ")}`;
389
+ return `pipr.config received invalid option value: ${z$1.prettifyError(error)}`;
390
+ }
391
+ function firstUnsupportedConfigFields(issues, parentPath) {
392
+ for (const issue of issues) {
393
+ const path = [...parentPath, ...issue.path];
394
+ if (issue.code === "unrecognized_keys") return {
395
+ path,
396
+ keys: issue.keys
397
+ };
398
+ if (issue.code === "invalid_union") for (const branchIssues of issue.errors) {
399
+ const unsupportedFields = firstUnsupportedConfigFields(branchIssues, path);
400
+ if (unsupportedFields) return unsupportedFields;
401
+ }
402
+ }
403
+ }
404
+ function piprConfigLabel(pathSegments) {
405
+ const path = pathSegments.join(".");
406
+ return path ? `pipr.config ${path}` : "pipr.config";
407
+ }
408
+ function reviewRecipeReviewerOptions(options, name) {
409
+ if (!options.model || !options.instructions) throw new Error("pipr.review requires model and instructions when reviewer is not provided");
410
+ return {
411
+ name,
412
+ model: options.model,
413
+ fallbacks: options.fallbacks,
414
+ instructions: options.instructions,
415
+ prompt: options.prompt,
416
+ tools: options.tools,
417
+ timeout: options.timeout
418
+ };
419
+ }
420
+ function createReviewer(api, options) {
421
+ return api.agent({
422
+ name: options.name ?? "reviewer",
423
+ model: options.model,
424
+ fallbacks: options.fallbacks,
425
+ instructions: options.instructions,
426
+ tools: options.tools ?? api.tools.readOnly,
427
+ output: api.schemas.review,
428
+ timeout: options.timeout,
429
+ prompt: options.prompt ?? (() => api.prompt`
430
+ Review this change.
431
+ `)
432
+ });
433
+ }
434
+ function createReviewRecipeTask(api, id, agent, options) {
435
+ return api.task({
436
+ name: id,
437
+ check: options.check,
438
+ async run(context) {
439
+ const manifest = await context.change.diffManifest({
440
+ compressed: true,
441
+ paths: options.paths
442
+ });
443
+ if (options.paths && manifest.files.length === 0) {
444
+ context.check.neutral("No changed files matched this review's path scope.");
445
+ await context.comment({ main: "No changed files matched this review's path scope." });
446
+ return;
447
+ }
448
+ const result = await context.pi.run(agent, {
449
+ manifest,
450
+ change: context.change
451
+ }, {
452
+ timeout: options.timeout,
453
+ paths: options.paths
454
+ });
455
+ const source = typeof options.comment === "function" ? await options.comment(result, {
456
+ review: { id },
457
+ repository: context.repository,
458
+ change: context.change,
459
+ platform: context.platform
460
+ }) : options.comment ?? defaultReviewComment(result);
461
+ await context.comment(source);
462
+ }
463
+ });
464
+ }
465
+ function defaultReviewComment(result) {
466
+ return {
467
+ main: defaultReviewMarkdown(result),
468
+ inlineFindings: result.inlineFindings
469
+ };
470
+ }
471
+ function defaultReviewMarkdown(result) {
472
+ const findings = result.inlineFindings.length === 0 ? "No inline findings." : result.inlineFindings.map((finding) => `- ${finding.body}`).join("\n");
473
+ return `## Summary\n\n${result.summary.body}\n\n## Findings\n\n${findings}`;
474
+ }
475
+ function registerReviewRecipeEntrypoints(api, task, options) {
476
+ const changeRequest = reviewChangeRequestEntrypoint(options);
477
+ if (changeRequest) api.on.changeRequest({
478
+ actions: changeRequest,
479
+ task
480
+ });
481
+ const command = reviewCommandEntrypoint(options);
482
+ if (command) api.command({
483
+ pattern: command.pattern,
484
+ ...command.options,
485
+ task
486
+ });
487
+ }
488
+ function reviewChangeRequestEntrypoint(options) {
489
+ const entrypoint = options.entrypoints?.changeRequest;
490
+ return entrypoint === false ? void 0 : entrypoint ?? [
491
+ "opened",
492
+ "updated",
493
+ "reopened",
494
+ "ready"
495
+ ];
496
+ }
497
+ function reviewCommandEntrypoint(options) {
498
+ const entrypoint = options.entrypoints?.command;
499
+ if (entrypoint === false) return;
500
+ if (typeof entrypoint === "object") return {
501
+ pattern: entrypoint.pattern ?? "@pipr review",
502
+ options: {
503
+ permission: entrypoint.permission ?? "write",
504
+ description: entrypoint.description
505
+ }
506
+ };
507
+ return {
508
+ pattern: entrypoint ?? "@pipr review",
509
+ options: { permission: "write" }
510
+ };
511
+ }
512
+ function mergePublicationConfig(target, next) {
513
+ if (!next) return;
514
+ if (next.maxInlineComments !== void 0) {
515
+ if (target.maxInlineComments !== void 0 && target.maxInlineComments !== next.maxInlineComments) throw new Error("pipr.config publication.maxInlineComments conflicts with existing value");
516
+ target.maxInlineComments = next.maxInlineComments;
517
+ }
518
+ if (next.autoResolve !== void 0) {
519
+ if (target.autoResolve !== void 0 && stableJson(target.autoResolve) !== stableJson(next.autoResolve)) throw new Error("pipr.config publication.autoResolve conflicts with existing value");
520
+ target.autoResolve = next.autoResolve;
521
+ }
522
+ }
523
+ function mergeConfigField(name, current, next) {
524
+ if (next === void 0) return current;
525
+ if (current !== void 0 && stableJson(current) !== stableJson(next)) throw new Error(`pipr.config ${name} conflicts with existing value`);
526
+ return next;
527
+ }
528
+ function mergeLimits(current, next) {
529
+ if (!next) return current;
530
+ assertRuntimeLimitConflicts(current, next);
531
+ return {
532
+ ...current,
533
+ ...next,
534
+ diffManifest: next.diffManifest ?? current?.diffManifest ? {
535
+ ...current?.diffManifest,
536
+ ...next.diffManifest
537
+ } : void 0
538
+ };
539
+ }
540
+ function assertRuntimeLimitConflicts(current, next) {
541
+ const currentRecord = current;
542
+ for (const [key, value] of Object.entries(next)) {
543
+ if (key === "diffManifest") continue;
544
+ if (value !== void 0 && currentRecord?.[key] !== void 0 && stableJson(currentRecord[key]) !== stableJson(value)) throw new Error(`pipr.config limits.${key} conflicts with existing value`);
545
+ }
546
+ assertDiffManifestLimitConflicts(current, next);
547
+ }
548
+ function assertDiffManifestLimitConflicts(current, next) {
549
+ if (current?.diffManifest && next.diffManifest) {
550
+ for (const [key, value] of Object.entries(next.diffManifest)) if (value !== void 0 && current.diffManifest[key] !== void 0 && current.diffManifest[key] !== value) throw new Error(`pipr.config limits.diffManifest.${key} conflicts with existing value`);
551
+ }
552
+ }
553
+ function createAgent(definition) {
554
+ return {
555
+ kind: "pipr.agent",
556
+ name: definition.name,
557
+ definition,
558
+ extend(patch) {
559
+ return createAgent({
560
+ ...definition,
561
+ ...patch,
562
+ instructions: patch.instructions === void 0 ? definition.instructions : {
563
+ kind: "pipr.prompt",
564
+ value: `${renderPromptValue(definition.instructions)}\n\n${renderPromptValue(patch.instructions)}`.trim()
565
+ }
566
+ });
567
+ }
568
+ };
569
+ }
570
+ function assertUnique(values, label) {
571
+ const seen = /* @__PURE__ */ new Set();
572
+ for (const value of values) {
573
+ if (seen.has(value)) throw new Error(`Duplicate ${label} '${value}'`);
574
+ seen.add(value);
575
+ }
576
+ }
577
+ function assertModelIdentity(models) {
578
+ assertNoDuplicateModelConfigs(models);
579
+ assertUniqueModelIds(models);
580
+ assertProviderModelAliasesDisambiguated(models);
581
+ }
582
+ function assertNoDuplicateModelConfigs(models) {
583
+ const effectiveConfigs = /* @__PURE__ */ new Map();
584
+ for (const model of models) {
585
+ const effectiveConfig = stableJson({
586
+ provider: model.provider,
587
+ model: model.model,
588
+ apiKeyEnv: model.apiKey?.name,
589
+ options: model.options
590
+ });
591
+ const existingConfigId = effectiveConfigs.get(effectiveConfig);
592
+ if (existingConfigId) throw new Error(`Duplicate model config for '${model.id}'. Reuse model '${existingConfigId}' instead.`);
593
+ effectiveConfigs.set(effectiveConfig, model.id);
594
+ }
595
+ }
596
+ function assertUniqueModelIds(models) {
597
+ const ids = /* @__PURE__ */ new Set();
598
+ for (const model of models) {
599
+ if (ids.has(model.id)) {
600
+ const providerModel = `${model.provider}/${model.model}`;
601
+ throw new Error(model.id === providerModel ? `Model '${providerModel}' is configured more than once with different options. Add an explicit id.` : `Duplicate model id '${model.id}'`);
602
+ }
603
+ ids.add(model.id);
604
+ }
605
+ }
606
+ function assertProviderModelAliasesDisambiguated(models) {
607
+ const providerModels = /* @__PURE__ */ new Map();
608
+ for (const model of models) {
609
+ const providerModel = `${model.provider}/${model.model}`;
610
+ const existingProviderModelId = providerModels.get(providerModel);
611
+ if (existingProviderModelId && (model.id === providerModel || existingProviderModelId === providerModel)) throw new Error(`Model '${providerModel}' is configured more than once with different options. Add an explicit id.`);
612
+ providerModels.set(providerModel, model.id);
613
+ }
614
+ }
615
+ function stableJson(value) {
616
+ return JSON.stringify(stableJsonValue(value));
617
+ }
618
+ function executeToolWithRuntimeInput(execute, options) {
619
+ return execute(options.ctx, options.input);
620
+ }
621
+ function stableJsonValue(value) {
622
+ if (Array.isArray(value)) return value.map(stableJsonValue);
623
+ if (typeof value === "object" && value !== null) return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== void 0).toSorted(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, stableJsonValue(item)]));
624
+ return value;
625
+ }
626
+ //#endregion
2
627
  export { definePipr, definePlugin, jsonSchema, md, parseReviewFinding, parseReviewResult, parseReviewSummary, reviewFindingSchema, reviewResultSchema, reviewSchemaExample, reviewSummarySchema, schema, schemas, z };
628
+
629
+ //# sourceMappingURL=index.mjs.map