@kortyx/prompts 0.0.0-stage → 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.js ADDED
@@ -0,0 +1,840 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ PROMPT_PROTOCOL_VERSION: () => PROMPT_PROTOCOL_VERSION,
34
+ PromptContentSchema: () => PromptContentSchema,
35
+ PromptError: () => PromptError,
36
+ PromptKeySchema: () => PromptKeySchema,
37
+ PromptMessageSchema: () => PromptMessageSchema,
38
+ PromptSnapshotSchema: () => PromptSnapshotSchema,
39
+ PromptTagSchema: () => PromptTagSchema,
40
+ PromptVersionSchema: () => PromptVersionSchema,
41
+ canonicalPromptJson: () => canonicalPromptJson,
42
+ compilePrompt: () => compilePrompt,
43
+ createPrompts: () => createPrompts,
44
+ definePrompt: () => definePrompt,
45
+ expandPromptMessages: () => expandPromptMessages,
46
+ isCompiledPrompt: () => isCompiledPrompt,
47
+ isPromptUsageMetadata: () => isPromptUsageMetadata,
48
+ localPromptSource: () => localPromptSource,
49
+ promptHash: () => promptHash,
50
+ promptReferencePattern: () => promptReferencePattern,
51
+ promptReferenceToken: () => promptReferenceToken,
52
+ promptReferences: () => promptReferences,
53
+ promptUsageMetadata: () => promptUsageMetadata,
54
+ promptVariables: () => promptVariables,
55
+ studioPromptSource: () => studioPromptSource,
56
+ validatePromptContent: () => validatePromptContent,
57
+ validatePromptValue: () => validatePromptValue,
58
+ verifyPromptSnapshot: () => verifyPromptSnapshot
59
+ });
60
+ module.exports = __toCommonJS(index_exports);
61
+
62
+ // src/compiler.ts
63
+ var import__ = __toESM(require("ajv/dist/2020"));
64
+
65
+ // src/contracts.ts
66
+ var import_zod = require("zod");
67
+ var PROMPT_PROTOCOL_VERSION = 1;
68
+ var PromptKeySchema = import_zod.z.string().min(1).max(200).regex(/^[a-zA-Z0-9][a-zA-Z0-9_./-]*$/);
69
+ var PromptTagSchema = import_zod.z.string().trim().min(1).max(64).regex(/^[a-z0-9][a-z0-9_-]*$/);
70
+ var PromptMessageSchema = import_zod.z.object({
71
+ role: import_zod.z.enum(["system", "user", "assistant"]),
72
+ content: import_zod.z.string().max(2e5)
73
+ }).strict();
74
+ var PromptContentSchema = import_zod.z.object({
75
+ format: import_zod.z.enum(["system-user", "chat"]),
76
+ messages: import_zod.z.array(PromptMessageSchema).min(1).max(100),
77
+ variablesSchema: import_zod.z.record(import_zod.z.string(), import_zod.z.json()),
78
+ configSchema: import_zod.z.record(import_zod.z.string(), import_zod.z.json()),
79
+ config: import_zod.z.record(import_zod.z.string(), import_zod.z.json()),
80
+ dependencies: import_zod.z.array(
81
+ import_zod.z.object({
82
+ id: PromptKeySchema,
83
+ version: import_zod.z.number().int().positive(),
84
+ hash: import_zod.z.string().regex(/^[a-f0-9]{64}$/)
85
+ }).strict()
86
+ ).max(100).default([])
87
+ }).strict().superRefine((value, ctx) => {
88
+ if (value.format === "system-user" && (value.messages.length !== 2 || value.messages[0]?.role !== "system" || value.messages[1]?.role !== "user"))
89
+ ctx.addIssue({
90
+ code: "custom",
91
+ message: "system-user requires exactly one system message followed by one user message.",
92
+ path: ["messages"]
93
+ });
94
+ });
95
+ var PromptVersionSchema = import_zod.z.object({
96
+ id: PromptKeySchema,
97
+ version: import_zod.z.number().int().positive(),
98
+ hash: import_zod.z.string().regex(/^[a-f0-9]{64}$/),
99
+ content: PromptContentSchema
100
+ });
101
+ var PromptSnapshotSchema = import_zod.z.object({
102
+ schemaVersion: import_zod.z.literal(PROMPT_PROTOCOL_VERSION),
103
+ environment: import_zod.z.string().min(1).max(128),
104
+ tag: PromptTagSchema.optional(),
105
+ revision: import_zod.z.string().min(1),
106
+ versions: import_zod.z.record(import_zod.z.string(), PromptVersionSchema),
107
+ source: import_zod.z.enum(["studio", "local", "cache", "fallback", "eval"]),
108
+ resolvedAt: import_zod.z.iso.datetime(),
109
+ requestedIds: import_zod.z.array(PromptKeySchema).max(100).optional()
110
+ }).strict();
111
+ var PromptError = class extends Error {
112
+ constructor(code, message, status = 400) {
113
+ super(message);
114
+ this.code = code;
115
+ this.status = status;
116
+ }
117
+ code;
118
+ status;
119
+ name = "PromptError";
120
+ };
121
+ function canonicalPromptJson(value) {
122
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
123
+ if (Array.isArray(value))
124
+ return `[${value.map(canonicalPromptJson).join(",")}]`;
125
+ return `{${Object.keys(value).sort().map(
126
+ (key) => `${JSON.stringify(key)}:${canonicalPromptJson(value[key])}`
127
+ ).join(",")}}`;
128
+ }
129
+ async function promptHash(content) {
130
+ const bytes = await crypto.subtle.digest(
131
+ "SHA-256",
132
+ new TextEncoder().encode(canonicalPromptJson(content))
133
+ );
134
+ return Array.from(
135
+ new Uint8Array(bytes),
136
+ (b) => b.toString(16).padStart(2, "0")
137
+ ).join("");
138
+ }
139
+ async function verifyPromptSnapshot(value) {
140
+ const parsed = PromptSnapshotSchema.safeParse(value);
141
+ if (!parsed.success)
142
+ throw new PromptError(
143
+ "PROMPT_SNAPSHOT_INVALID",
144
+ "The prompt snapshot does not match the serving contract."
145
+ );
146
+ const snapshot = parsed.data;
147
+ if (Object.keys(snapshot.versions).length > 200)
148
+ throw new PromptError(
149
+ "PROMPT_SNAPSHOT_LIMIT",
150
+ "A snapshot supports at most 200 versions."
151
+ );
152
+ for (const [key, version] of Object.entries(snapshot.versions)) {
153
+ if (key !== version.id || await promptHash(version.content) !== version.hash)
154
+ throw new PromptError(
155
+ "PROMPT_HASH_MISMATCH",
156
+ `Prompt ${key} did not match its immutable hash.`
157
+ );
158
+ for (const dependency of version.content.dependencies) {
159
+ const actual = snapshot.versions[dependency.id];
160
+ if (!actual || actual.version !== dependency.version || actual.hash !== dependency.hash)
161
+ throw new PromptError(
162
+ "PROMPT_DEPENDENCY_MISMATCH",
163
+ `Missing exact dependency ${dependency.id}@${dependency.version}.`
164
+ );
165
+ }
166
+ }
167
+ const visit = (key, active, done2) => {
168
+ if (active.has(key))
169
+ throw new PromptError(
170
+ "PROMPT_DEPENDENCY_CYCLE",
171
+ `Prompt dependency cycle at ${key}.`
172
+ );
173
+ if (done2.has(key)) return;
174
+ active.add(key);
175
+ for (const dep of snapshot.versions[key]?.content.dependencies ?? [])
176
+ visit(dep.id, active, done2);
177
+ active.delete(key);
178
+ done2.add(key);
179
+ };
180
+ const done = /* @__PURE__ */ new Set();
181
+ for (const key of Object.keys(snapshot.versions)) visit(key, /* @__PURE__ */ new Set(), done);
182
+ const freeze = (value2) => {
183
+ if (value2 && typeof value2 === "object" && !Object.isFrozen(value2)) {
184
+ for (const nested of Object.values(value2)) freeze(nested);
185
+ Object.freeze(value2);
186
+ }
187
+ };
188
+ freeze(snapshot);
189
+ return snapshot;
190
+ }
191
+
192
+ // src/references.ts
193
+ var promptReferencePattern = /\[\[prompt:([a-zA-Z0-9][a-zA-Z0-9_./-]*)\]\]/g;
194
+ function promptReferenceToken(id) {
195
+ return `[[prompt:${id}]]`;
196
+ }
197
+ function promptReferences(content) {
198
+ return [
199
+ ...new Set(
200
+ content.messages.flatMap(
201
+ (message) => [...message.content.matchAll(promptReferencePattern)].map(
202
+ (match) => match[1]
203
+ )
204
+ )
205
+ )
206
+ ];
207
+ }
208
+ function expandPromptMessages(root, versions = {}) {
209
+ const used = /* @__PURE__ */ new Map();
210
+ const cache = /* @__PURE__ */ new Map();
211
+ const expand = (owner, text, role, active) => {
212
+ const parts = [];
213
+ let size = 0, position = 0;
214
+ const append = (part) => {
215
+ size += part.length;
216
+ if (size > 2e5)
217
+ throw new PromptError(
218
+ "PROMPT_REFERENCE_LIMIT",
219
+ "An expanded message cannot exceed 200,000 characters."
220
+ );
221
+ parts.push(part);
222
+ };
223
+ for (const match of text.matchAll(promptReferencePattern)) {
224
+ append(text.slice(position, match.index));
225
+ position = match.index + match[0].length;
226
+ const id = match[1];
227
+ const dependency = owner.content.dependencies.find(
228
+ (item) => item.id === id
229
+ );
230
+ const child = versions[id];
231
+ if (!dependency || !child || child.version !== dependency.version || child.hash !== dependency.hash)
232
+ throw new PromptError(
233
+ "PROMPT_REFERENCE_MISSING",
234
+ `Include an exact dependency for referenced prompt ${id}.`
235
+ );
236
+ if (active.has(id) || active.size >= 200)
237
+ throw new PromptError(
238
+ "PROMPT_DEPENDENCY_CYCLE",
239
+ `Prompt reference cycle at ${id}.`
240
+ );
241
+ const messages = child.content.messages.filter(
242
+ (message) => message.role === role
243
+ );
244
+ if (!messages.length)
245
+ throw new PromptError(
246
+ "PROMPT_REFERENCE_ROLE",
247
+ `${id} has no ${role} message to include.`
248
+ );
249
+ used.set(id, child);
250
+ const cacheKey = `${id}@${child.version}:${role}`;
251
+ let included = cache.get(cacheKey);
252
+ if (included === void 0) {
253
+ const next = new Set(active).add(id);
254
+ const chunks = [];
255
+ let length = 0;
256
+ for (const message of messages) {
257
+ const chunk = expand(child, message.content, role, next);
258
+ length += chunk.length + (chunks.length ? 2 : 0);
259
+ if (length > 2e5)
260
+ throw new PromptError(
261
+ "PROMPT_REFERENCE_LIMIT",
262
+ "An expanded message cannot exceed 200,000 characters."
263
+ );
264
+ chunks.push(chunk);
265
+ }
266
+ included = chunks.join("\n\n");
267
+ cache.set(cacheKey, included);
268
+ }
269
+ append(included);
270
+ }
271
+ append(text.slice(position));
272
+ return parts.join("");
273
+ };
274
+ return {
275
+ messages: root.content.messages.map((message) => ({
276
+ ...message,
277
+ content: expand(root, message.content, message.role, /* @__PURE__ */ new Set([root.id]))
278
+ })),
279
+ used: [...used.values()]
280
+ };
281
+ }
282
+
283
+ // src/compiler.ts
284
+ var ajv = new import__.default({
285
+ strict: false,
286
+ allErrors: true,
287
+ validateFormats: false,
288
+ ownProperties: true
289
+ });
290
+ var validators = /* @__PURE__ */ new Map();
291
+ var compiledPrompts = /* @__PURE__ */ new WeakSet();
292
+ var usageMetadata = /* @__PURE__ */ new WeakSet();
293
+ function isCompiledPrompt(value) {
294
+ return Boolean(
295
+ value && typeof value === "object" && compiledPrompts.has(value)
296
+ );
297
+ }
298
+ function promptUsageMetadata(prompt) {
299
+ if (!isCompiledPrompt(prompt))
300
+ throw new PromptError(
301
+ "PROMPT_REASON_INVALID",
302
+ "Use a compiled prompt returned by usePrompt."
303
+ );
304
+ const metadata = Object.freeze({
305
+ id: prompt.ref.id,
306
+ version: prompt.ref.version,
307
+ hash: prompt.ref.hash,
308
+ environment: prompt.ref.environment,
309
+ snapshotRevision: prompt.ref.snapshotRevision,
310
+ ...prompt.dependencies?.length ? { dependencies: prompt.dependencies } : {}
311
+ });
312
+ usageMetadata.add(metadata);
313
+ return metadata;
314
+ }
315
+ function isPromptUsageMetadata(value) {
316
+ return Boolean(
317
+ value && typeof value === "object" && usageMetadata.has(value)
318
+ );
319
+ }
320
+ function freezeValue(value, seen = /* @__PURE__ */ new WeakSet()) {
321
+ if (value && typeof value === "object" && !seen.has(value)) {
322
+ seen.add(value);
323
+ for (const item of Object.values(value)) freezeValue(item, seen);
324
+ Object.freeze(value);
325
+ }
326
+ return value;
327
+ }
328
+ function validatePromptValue(schema, value, label) {
329
+ const key = canonicalPromptJson(schema);
330
+ let validator = validators.get(key);
331
+ if (!validator) {
332
+ if (validators.size >= 256) validators.clear();
333
+ try {
334
+ validator = ajv.compile(schema);
335
+ } catch {
336
+ throw new PromptError(
337
+ "PROMPT_SCHEMA_INVALID",
338
+ `${label} schema is invalid.`
339
+ );
340
+ }
341
+ validators.set(key, validator);
342
+ }
343
+ if (!validator(value))
344
+ throw new PromptError(
345
+ "PROMPT_CONTRACT_MISMATCH",
346
+ `${label}: ${ajv.errorsText(validator.errors)}.`
347
+ );
348
+ }
349
+ var placeholder = /\{\{\s*([A-Za-z_][A-Za-z0-9_.]*)\s*\}\}/g;
350
+ function promptVariables(content) {
351
+ return [
352
+ ...new Set(
353
+ content.messages.flatMap(
354
+ (message) => [...message.content.matchAll(placeholder)].map(
355
+ (match) => match[1]
356
+ )
357
+ )
358
+ )
359
+ ];
360
+ }
361
+ function validatePromptContent(content) {
362
+ validatePromptValue(content.configSchema, content.config, "Configuration");
363
+ try {
364
+ ajv.compile(content.variablesSchema);
365
+ } catch {
366
+ throw new PromptError(
367
+ "PROMPT_SCHEMA_INVALID",
368
+ "Template input schema is invalid."
369
+ );
370
+ }
371
+ const inputs = content.variablesSchema.properties;
372
+ for (const variable of promptVariables(content)) {
373
+ const top = variable.split(".")[0];
374
+ if (!inputs || !Object.hasOwn(inputs, top))
375
+ throw new PromptError(
376
+ "PROMPT_VARIABLE_UNKNOWN",
377
+ `Declare template input ${top} in the input schema.`
378
+ );
379
+ if (Object.hasOwn(content.config, top))
380
+ throw new PromptError(
381
+ "PROMPT_VARIABLE_CONFIG_OVERLAP",
382
+ `${top} cannot be both a template input and saved configuration.`
383
+ );
384
+ }
385
+ }
386
+ function definePrompt(reference) {
387
+ if (!reference.id || !reference.variables || !reference.config)
388
+ throw new PromptError(
389
+ "PROMPT_REFERENCE_INVALID",
390
+ "A prompt reference needs id, variables and config schemas."
391
+ );
392
+ return Object.freeze({ ...reference });
393
+ }
394
+ function compilePrompt(ref, version, variables, identity, versions) {
395
+ if (version.id !== ref.id || version.content.format !== ref.format)
396
+ throw new PromptError(
397
+ "PROMPT_CONTRACT_MISMATCH",
398
+ `Prompt ${ref.id} format or identity differs from the application contract.`
399
+ );
400
+ const values = ref.variables.parse(variables);
401
+ validatePromptValue(
402
+ version.content.variablesSchema,
403
+ values,
404
+ "Template inputs"
405
+ );
406
+ validatePromptValue(
407
+ version.content.configSchema,
408
+ version.content.config,
409
+ "Configuration"
410
+ );
411
+ const config = ref.config.parse(structuredClone(version.content.config));
412
+ const expanded = expandPromptMessages(version, versions);
413
+ if (expanded.used.length)
414
+ validatePromptContent({ ...version.content, messages: expanded.messages });
415
+ for (const child of expanded.used) {
416
+ const properties = child.content.variablesSchema.properties;
417
+ const childValues = properties && typeof properties === "object" && !Array.isArray(properties) ? Object.fromEntries(
418
+ Object.keys(properties).filter((key) => Object.hasOwn(values, key)).map((key) => [key, values[key]])
419
+ ) : values;
420
+ validatePromptValue(
421
+ child.content.variablesSchema,
422
+ childValues,
423
+ `Inputs for ${child.id}`
424
+ );
425
+ }
426
+ const messages = expanded.messages.map((message) => ({
427
+ role: message.role,
428
+ content: message.content.replace(placeholder, (_, path) => {
429
+ let value = values;
430
+ for (const key of path.split(".")) {
431
+ if (["__proto__", "constructor", "prototype"].includes(key) || !value || typeof value !== "object" || !Object.hasOwn(value, key))
432
+ throw new PromptError(
433
+ "PROMPT_VARIABLE_MISSING",
434
+ `Missing template input ${path}.`
435
+ );
436
+ value = value[key];
437
+ }
438
+ if (value === void 0)
439
+ throw new PromptError(
440
+ "PROMPT_VARIABLE_MISSING",
441
+ `Missing template input ${path}.`
442
+ );
443
+ return typeof value === "string" ? value : canonicalPromptJson(value);
444
+ })
445
+ }));
446
+ const compiled = freezeValue({
447
+ format: ref.format,
448
+ messages,
449
+ config,
450
+ ...expanded.used.length ? {
451
+ dependencies: expanded.used.map(({ id, version: version2, hash }) => ({
452
+ id,
453
+ version: version2,
454
+ hash
455
+ }))
456
+ } : {},
457
+ ...ref.format === "system-user" ? { system: messages[0]?.content, user: messages[1]?.content } : {},
458
+ ref: {
459
+ id: ref.id,
460
+ version: version.version,
461
+ hash: version.hash,
462
+ ...identity
463
+ }
464
+ });
465
+ compiledPrompts.add(compiled);
466
+ return compiled;
467
+ }
468
+
469
+ // src/usage.ts
470
+ var import_node = require("@kortyx/usage-telemetry/node");
471
+
472
+ // package.json
473
+ var package_default = {
474
+ name: "@kortyx/prompts",
475
+ version: "0.2.0",
476
+ description: "Typed, versioned prompts and portable Studio serving for Kortyx.",
477
+ license: "Apache-2.0",
478
+ main: "./dist/index.js",
479
+ types: "./dist/index.d.ts",
480
+ exports: {
481
+ ".": {
482
+ types: "./dist/index.d.ts",
483
+ default: "./dist/index.js"
484
+ },
485
+ "./browser": {
486
+ types: "./dist/browser.d.ts",
487
+ default: "./dist/browser.js"
488
+ }
489
+ },
490
+ files: [
491
+ "dist/**",
492
+ "README.md",
493
+ "CHANGELOG.md"
494
+ ],
495
+ scripts: {
496
+ build: "tsup",
497
+ dev: "tsc --watch",
498
+ "type-check": "tsc --noEmit -p tsconfig.test.json",
499
+ test: "vitest run && pnpm -s type-check",
500
+ coverage: "vitest run --coverage && pnpm -s type-check",
501
+ lint: "biome check src test"
502
+ },
503
+ dependencies: {
504
+ ajv: "8.18.0",
505
+ zod: "catalog:",
506
+ "@kortyx/usage-telemetry": "workspace:*"
507
+ },
508
+ devDependencies: {
509
+ tsup: "catalog:",
510
+ typescript: "catalog:",
511
+ vitest: "catalog:",
512
+ "@vitest/coverage-v8": "catalog:",
513
+ "@types/node": "catalog:"
514
+ },
515
+ sideEffects: false,
516
+ publishConfig: {
517
+ access: "public"
518
+ },
519
+ repository: {
520
+ type: "git",
521
+ url: "https://github.com/kortyx-io/kortyx.git",
522
+ directory: "packages/prompts"
523
+ }
524
+ };
525
+
526
+ // src/usage.ts
527
+ var client = () => (0, import_node.getUsageTelemetry)("prompts", package_default.version);
528
+ var usage = {
529
+ start: (...args) => client().start(...args),
530
+ feature: (...args) => client().feature(...args),
531
+ measure: (...args) => client().measure(...args),
532
+ flush: () => client().flush()
533
+ };
534
+
535
+ // src/source.ts
536
+ function createPrompts(options) {
537
+ const ids = options.definitions.map((ref) => ref.id), tag = PromptTagSchema.parse(options.source.tag ?? "live"), environment = options.environment ?? options.source.environment ?? "production";
538
+ if (options.maxStaleMs !== void 0 && (!Number.isFinite(options.maxStaleMs) || options.maxStaleMs < 0))
539
+ throw new PromptError(
540
+ "PROMPT_CACHE_INVALID",
541
+ "maxStaleMs must be a finite, nonnegative duration."
542
+ );
543
+ if (options.source.environment && options.source.environment !== environment)
544
+ throw new PromptError(
545
+ "PROMPT_ENVIRONMENT_MISMATCH",
546
+ "Source and manager environments must match."
547
+ );
548
+ if (new Set(ids).size !== ids.length)
549
+ throw new PromptError(
550
+ "PROMPT_REFERENCE_DUPLICATE",
551
+ "Registered prompt IDs must be unique."
552
+ );
553
+ let lastGood;
554
+ const cacheKey = `${options.source.identity}:${environment}:${tag}:${JSON.stringify([...ids].sort())}`;
555
+ const fresh = async (signal) => {
556
+ const pending = options.source.resolve(ids, { environment, tag, ...signal ? { signal } : {} }).then(verifyPromptSnapshot).then(async (value) => {
557
+ for (const id of ids)
558
+ if (!value.versions[id])
559
+ throw new PromptError(
560
+ "PROMPT_NOT_ASSIGNED",
561
+ `No ${tag} tag for ${id}.`,
562
+ 404
563
+ );
564
+ if (value.environment !== environment)
565
+ throw new PromptError(
566
+ "PROMPT_ENVIRONMENT_MISMATCH",
567
+ "Source returned a different environment."
568
+ );
569
+ if ((value.tag ?? "live") !== tag)
570
+ throw new PromptError(
571
+ "PROMPT_TAG_MISMATCH",
572
+ "Source returned a different prompt tag."
573
+ );
574
+ lastGood = value;
575
+ await options.cache?.set(cacheKey, value);
576
+ return value;
577
+ });
578
+ try {
579
+ return await pending;
580
+ } catch (error) {
581
+ if (signal?.aborted || options.fallback !== "last-known-good" || error instanceof PromptError && error.status < 500 && error.status !== 429)
582
+ throw error;
583
+ const cached = lastGood ?? await options.cache?.get(cacheKey);
584
+ if (!cached || cached.environment !== environment || (cached.tag ?? "live") !== tag || Date.parse(cached.resolvedAt) > Date.now() + 3e4 || Date.now() - Date.parse(cached.resolvedAt) > (options.maxStaleMs ?? 3e5))
585
+ throw error;
586
+ return {
587
+ ...await verifyPromptSnapshot(cached),
588
+ source: "fallback"
589
+ };
590
+ }
591
+ };
592
+ return {
593
+ definitions: options.definitions,
594
+ start(start = {}) {
595
+ let snapshotPromise;
596
+ const snapshot = (stored) => {
597
+ if (snapshotPromise) return snapshotPromise;
598
+ snapshotPromise = stored ? verifyPromptSnapshot(stored).then((value) => {
599
+ if (value.environment !== environment)
600
+ throw new PromptError(
601
+ "PROMPT_ENVIRONMENT_MISMATCH",
602
+ "Stored snapshot belongs to another environment."
603
+ );
604
+ return value;
605
+ }) : start.snapshot ? verifyPromptSnapshot(start.snapshot).then((value) => {
606
+ if (value.source !== "eval" || value.environment !== environment)
607
+ throw new PromptError(
608
+ "PROMPT_EVAL_SNAPSHOT_INVALID",
609
+ "An eval snapshot must match the configured environment."
610
+ );
611
+ return value;
612
+ }) : fresh(start.signal);
613
+ return snapshotPromise;
614
+ };
615
+ const pins = /* @__PURE__ */ new Map();
616
+ const pin = async (id, version, stored) => {
617
+ const baseline = await snapshot();
618
+ if (baseline.source === "eval" && baseline.versions[id]?.version !== version)
619
+ throw new PromptError(
620
+ "PROMPT_EVAL_PIN_CONFLICT",
621
+ `Pinned ${id}@${version} conflicts with the eval snapshot.`
622
+ );
623
+ const key = `${id}@${version}`;
624
+ let pendingPin = pins.get(key);
625
+ if (!pendingPin) {
626
+ pendingPin = (stored ? verifyPromptSnapshot(stored) : baseline.versions[id]?.version === version ? Promise.resolve(baseline) : options.source.resolve([id], {
627
+ environment,
628
+ tag,
629
+ versions: { [id]: version },
630
+ ...start.signal ? { signal: start.signal } : {}
631
+ }).then(verifyPromptSnapshot)).then((value) => {
632
+ if (value.environment !== environment || value.versions[id]?.version !== version)
633
+ throw new PromptError(
634
+ "PROMPT_PIN_MISMATCH",
635
+ `Source returned a different pin for ${id}.`
636
+ );
637
+ return value;
638
+ });
639
+ pins.set(key, pendingPin);
640
+ }
641
+ return pendingPin;
642
+ };
643
+ return {
644
+ snapshot,
645
+ pin,
646
+ recordUsage(usage2) {
647
+ start.onUsage?.(usage2);
648
+ },
649
+ async resolve(ref, args) {
650
+ const scope = usage.start("sdk.prompt.resolve");
651
+ try {
652
+ if (!ids.includes(ref.id))
653
+ throw new PromptError(
654
+ "PROMPT_NOT_REGISTERED",
655
+ `Register ${ref.id} with createPrompts.`
656
+ );
657
+ const resolved = await snapshot(args.stored);
658
+ let selectedSnapshot = resolved;
659
+ let selected = resolved.versions[ref.id];
660
+ if (args.version !== void 0 && selected?.version !== args.version) {
661
+ const pinned = await pin(ref.id, args.version, args.pin);
662
+ selectedSnapshot = pinned;
663
+ selected = pinned.versions[ref.id];
664
+ if (selected?.version !== args.version)
665
+ throw new PromptError(
666
+ "PROMPT_PIN_MISMATCH",
667
+ `Source returned a different version of ${ref.id}.`
668
+ );
669
+ }
670
+ if (!selected)
671
+ throw new PromptError(
672
+ "PROMPT_NOT_ASSIGNED",
673
+ `No version resolved for ${ref.id}.`,
674
+ 404
675
+ );
676
+ const compiled = compilePrompt(
677
+ ref,
678
+ selected,
679
+ args.variables,
680
+ {
681
+ source: selectedSnapshot.source,
682
+ environment: selectedSnapshot.environment,
683
+ snapshotRevision: selectedSnapshot.revision
684
+ },
685
+ selectedSnapshot.versions
686
+ );
687
+ scope.end("success", { source: selectedSnapshot.source });
688
+ return compiled;
689
+ } catch (error) {
690
+ scope.fail(error);
691
+ throw error;
692
+ }
693
+ }
694
+ };
695
+ }
696
+ };
697
+ }
698
+ function studioPromptSource(options) {
699
+ const url = new URL(options.apiUrl);
700
+ if (!/^https?:$/.test(url.protocol) || url.username || url.password)
701
+ throw new PromptError(
702
+ "PROMPT_SOURCE_INVALID",
703
+ "Use an HTTP API origin without embedded credentials."
704
+ );
705
+ const request = options.fetch ?? fetch;
706
+ const publicKeyId = /^ktyx_(?:test|live)_([^_]+)_/.exec(options.apiKey)?.[1] ?? crypto.randomUUID();
707
+ return {
708
+ identity: `${url.origin}${url.pathname}:${options.projectId ?? ""}:${publicKeyId}:${options.environmentId ?? ""}`,
709
+ tag: PromptTagSchema.parse(options.tag ?? "live"),
710
+ ...options.environment ? { environment: options.environment } : {},
711
+ async resolve(ids, args) {
712
+ for (let attempt = 0; ; attempt++) {
713
+ try {
714
+ const signal = AbortSignal.any([
715
+ AbortSignal.timeout(options.timeoutMs ?? 5e3),
716
+ ...args.signal ? [args.signal] : []
717
+ ]);
718
+ const response = await request(
719
+ `${options.apiUrl.replace(/\/$/, "")}/v1/prompts/resolve`,
720
+ {
721
+ method: "POST",
722
+ signal,
723
+ headers: {
724
+ authorization: `Bearer ${options.apiKey}`,
725
+ "content-type": "application/json",
726
+ ...options.projectId ? { "x-kortyx-project-id": options.projectId } : {},
727
+ ...options.environmentId ? { "x-kortyx-environment-id": options.environmentId } : {}
728
+ },
729
+ body: JSON.stringify({
730
+ schemaVersion: 1,
731
+ ids,
732
+ tag: options.tag ?? args.tag ?? "live",
733
+ environment: options.environment ?? args.environment,
734
+ ...args.versions ? { versions: args.versions } : {}
735
+ })
736
+ }
737
+ );
738
+ if (!response.ok)
739
+ throw new PromptError(
740
+ "PROMPT_SOURCE_HTTP",
741
+ `Prompt serving failed (HTTP ${response.status}).`,
742
+ response.status
743
+ );
744
+ const reader = response.body?.getReader();
745
+ if (!reader)
746
+ throw new PromptError(
747
+ "PROMPT_SNAPSHOT_INVALID",
748
+ "Prompt serving returned an empty response."
749
+ );
750
+ const decoder = new TextDecoder();
751
+ let body = "", bytes = 0;
752
+ try {
753
+ for (; ; ) {
754
+ const chunk = await reader.read();
755
+ if (chunk.done) break;
756
+ bytes += chunk.value.byteLength;
757
+ if (bytes > 20 * 1024 * 1024)
758
+ throw new PromptError(
759
+ "PROMPT_SNAPSHOT_LIMIT",
760
+ "Prompt serving exceeded the 20 MiB response limit."
761
+ );
762
+ body += decoder.decode(chunk.value, { stream: true });
763
+ }
764
+ body += decoder.decode();
765
+ let value;
766
+ try {
767
+ value = JSON.parse(body);
768
+ } catch {
769
+ throw new PromptError(
770
+ "PROMPT_SNAPSHOT_INVALID",
771
+ "Prompt serving returned invalid JSON."
772
+ );
773
+ }
774
+ return await verifyPromptSnapshot(value);
775
+ } finally {
776
+ await reader.cancel().catch(() => {
777
+ });
778
+ reader.releaseLock();
779
+ }
780
+ } catch (error) {
781
+ if (args.signal?.aborted || attempt >= (options.retries ?? 1) || error instanceof PromptError && error.status < 500 && error.status !== 429)
782
+ throw error;
783
+ }
784
+ }
785
+ }
786
+ };
787
+ }
788
+ function localPromptSource(snapshot) {
789
+ return {
790
+ identity: `local:${snapshot.revision}`,
791
+ environment: snapshot.environment,
792
+ tag: snapshot.tag ?? "live",
793
+ async resolve(ids, args) {
794
+ const verified = await verifyPromptSnapshot(snapshot);
795
+ if (args.environment !== verified.environment)
796
+ throw new PromptError(
797
+ "PROMPT_ENVIRONMENT_MISMATCH",
798
+ "Local snapshot environment differs from the requested environment."
799
+ );
800
+ for (const id of ids)
801
+ if (!verified.versions[id] || args.versions?.[id] && verified.versions[id]?.version !== args.versions[id])
802
+ throw new PromptError(
803
+ "PROMPT_NOT_FOUND",
804
+ `Local snapshot does not contain ${id}.`,
805
+ 404
806
+ );
807
+ return { ...verified, source: "local" };
808
+ }
809
+ };
810
+ }
811
+ // Annotate the CommonJS export names for ESM import in node:
812
+ 0 && (module.exports = {
813
+ PROMPT_PROTOCOL_VERSION,
814
+ PromptContentSchema,
815
+ PromptError,
816
+ PromptKeySchema,
817
+ PromptMessageSchema,
818
+ PromptSnapshotSchema,
819
+ PromptTagSchema,
820
+ PromptVersionSchema,
821
+ canonicalPromptJson,
822
+ compilePrompt,
823
+ createPrompts,
824
+ definePrompt,
825
+ expandPromptMessages,
826
+ isCompiledPrompt,
827
+ isPromptUsageMetadata,
828
+ localPromptSource,
829
+ promptHash,
830
+ promptReferencePattern,
831
+ promptReferenceToken,
832
+ promptReferences,
833
+ promptUsageMetadata,
834
+ promptVariables,
835
+ studioPromptSource,
836
+ validatePromptContent,
837
+ validatePromptValue,
838
+ verifyPromptSnapshot
839
+ });
840
+ //# sourceMappingURL=index.js.map