@svara/prompts 0.1.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/README.md ADDED
@@ -0,0 +1,304 @@
1
+ # prompt
2
+
3
+ A tiny, ergonomic tagged-template utility for assembling LLM prompts in TypeScript.
4
+
5
+ It turns this:
6
+
7
+ ```ts
8
+ const p = ai`
9
+ You are a helpful assistant.
10
+
11
+ Context:
12
+ > ${context}
13
+
14
+ Items:
15
+ - ${items}
16
+
17
+ Schema:
18
+ ${UserSchema}
19
+ `
20
+ ```
21
+
22
+ ...into a clean, well-indented prompt string ready to send to a model.
23
+
24
+ The library is intentionally small: a single tagged template (`prompt` / `ai`) plus a factory (`promptCreate`) for advanced configuration. All conditionals, loops, and composition stay in plain JavaScript inside `${...}` — no DSL, no helpers to learn.
25
+
26
+ ## Install
27
+
28
+ Copy `prompt.ts` into your project. No runtime dependencies.
29
+
30
+ ## Quick start
31
+
32
+ ```ts
33
+ import { prompt, ai } from './prompt'
34
+
35
+ const p = ai`
36
+ Summarize the following text in one sentence.
37
+
38
+ Text:
39
+ > ${userInput}
40
+ `
41
+ ```
42
+
43
+ `prompt` and `ai` are the same function — `ai` is just an alias that reads nicely at call sites.
44
+
45
+ ## How it works
46
+
47
+ The tag walks the template and, for each interpolated value, captures the **prefix of the line that value sits on**. Every line of the rendered value is then re-emitted with that prefix. This is what makes lists, block quotes, and multi-line interpolations look right:
48
+
49
+ ```ts
50
+ ai`
51
+ Items:
52
+ - ${['apple', 'banana', 'cherry']}
53
+ `
54
+ // Items:
55
+ // - apple
56
+ // - banana
57
+ // - cherry
58
+
59
+ ai`
60
+ > ${"line one\nline two"}
61
+ `
62
+ // > line one
63
+ // > line two
64
+ ```
65
+
66
+ After assembly the output is cleaned up:
67
+
68
+ - common leading indentation is stripped (auto-dedent)
69
+ - whitespace-only lines are collapsed to empty
70
+ - runs of 3+ blank lines are reduced to 2
71
+ - the result is trimmed
72
+
73
+ ## Interpolated value types
74
+
75
+ `flatten` decides how each `${value}` renders. Detection order:
76
+
77
+ 1. **`renderPrompt` symbol** — your own opt-in render protocol (see below).
78
+ 2. **`JsonValue`** — explicit "render me as a JSON code fence" wrapper.
79
+ 3. **Zod-like** — anything exposing `toJSONSchema()` (e.g. Zod schemas) renders as its JSON Schema in a fenced block.
80
+ 4. **Arrays** — each element is flattened and joined with newlines. `null`, `undefined`, and `false` are dropped; `0` and `''` are kept.
81
+ 5. **Other objects** — `JSON.stringify`d in a ```` ```json ```` fence.
82
+ 6. **Primitives** — `String(value).trim()`.
83
+ 7. **Nullish / `false`** — empty string.
84
+
85
+ ### `JsonValue<T>`
86
+
87
+ Wrap a value to force JSON-fence rendering even when it would otherwise be treated as a primitive or skipped:
88
+
89
+ ```ts
90
+ import { JsonValue, ai } from './prompt'
91
+
92
+ ai`
93
+ Config:
94
+ ${new JsonValue({ retries: 3, timeoutMs: 5_000 })}
95
+ `
96
+ ```
97
+
98
+ ### Zod (and other schema libs)
99
+
100
+ Any object with a `toJSONSchema()` method is rendered as its schema. Zod v4 is the typical case:
101
+
102
+ ```ts
103
+ import { z } from 'zod'
104
+
105
+ const UserSchema = z.object({ id: z.string(), name: z.string() })
106
+
107
+ ai`
108
+ Return data matching this schema:
109
+ ${UserSchema}
110
+ `
111
+ ```
112
+
113
+ ### Custom render protocol
114
+
115
+ For your own classes, opt in via the exported `renderPrompt` symbol:
116
+
117
+ ```ts
118
+ import { renderPrompt, ai } from './prompt'
119
+
120
+ class Document {
121
+ constructor(public title: string, public body: string) {}
122
+ [renderPrompt]() {
123
+ return ai`
124
+ # ${this.title}
125
+
126
+ ${this.body}
127
+ `
128
+ }
129
+ }
130
+
131
+ ai`
132
+ Read the document below and answer the question.
133
+
134
+ ${new Document('Onboarding', '...')}
135
+ `
136
+ ```
137
+
138
+ The value returned from `[renderPrompt]()` is itself a `PromptValue` and gets re-flattened — so you can return a string, an array, another `ai\`\`` result, or a `JsonValue`.
139
+
140
+ ## The factory: `promptCreate(options?)`
141
+
142
+ `promptCreate` returns a configured `prompt` tag. With no arguments it behaves identically to bare `prompt`.
143
+
144
+ ```ts
145
+ import { promptCreate } from './prompt'
146
+
147
+ const ai = promptCreate({
148
+ dedent: true,
149
+ maxChars: 8_000,
150
+ truncate: 'end',
151
+ })
152
+
153
+ ai`...`
154
+ ```
155
+
156
+ ### Options
157
+
158
+ All options are optional and flat.
159
+
160
+ ```ts
161
+ type TruncateMode = 'end' | 'start' | 'middle'
162
+ type TruncateMarker = string | ((removedChars: number) => string)
163
+
164
+ interface TruncateCtx {
165
+ maxChars: number
166
+ mode: TruncateMode
167
+ marker: TruncateMarker
168
+ truncate: (text: string, mode?: TruncateMode) => string
169
+ }
170
+
171
+ type OutputTruncateFn = (text: string, ctx: TruncateCtx) => string
172
+ type ValueTruncateFn = (rendered: string, original: PromptValue, ctx: TruncateCtx) => string
173
+
174
+ type OutputTruncate =
175
+ | { maxChars: number; truncate?: TruncateMode; marker?: TruncateMarker }
176
+ | { truncate: OutputTruncateFn; maxChars?: number; marker?: TruncateMarker }
177
+
178
+ type ValueTruncate =
179
+ | { valueMaxChars: number; valueTruncate?: TruncateMode; valueMarker?: TruncateMarker }
180
+ | { valueTruncate: ValueTruncateFn; valueMaxChars?: number; valueMarker?: TruncateMarker }
181
+
182
+ type PromptOptions =
183
+ { dedent?: boolean }
184
+ & Partial<OutputTruncate>
185
+ & Partial<ValueTruncate>
186
+ ```
187
+
188
+ | Option | Default | Effect |
189
+ |-------------------|-----------------------------|------------------------------------------------------------------------|
190
+ | `dedent` | `true` | Strip common leading indentation from the static template parts. |
191
+ | `maxChars` | — | Cap on the whole assembled output. Required when `truncate` is a mode. |
192
+ | `truncate` | `'end'` (when `maxChars` set) | Built-in mode or full custom function for the whole-output pass. |
193
+ | `marker` | `…[truncated N chars]` | Marker for the whole-output pass. String or `(removed) => string`. |
194
+ | `valueMaxChars` | — | Per-value cap. Required when `valueTruncate` is a mode. |
195
+ | `valueTruncate` | `'end'` (when `valueMaxChars` set) | Built-in mode or full custom function for the per-value pass. |
196
+ | `valueMarker` | `…[truncated N chars]` | Marker for the per-value pass. |
197
+
198
+ The discriminated unions enforce at compile time that:
199
+ - a mode string requires its matching `maxChars` (or `valueMaxChars`),
200
+ - a function in `truncate` / `valueTruncate` takes over and may read `maxChars` / `marker` from `ctx` or ignore them.
201
+
202
+ ### Truncation passes
203
+
204
+ Both passes are independent and both can fire on the same render:
205
+
206
+ 1. **Per-value pass** runs inside `flatten`, on each interpolated value after it is rendered to a string.
207
+ 2. **Whole-output pass** runs once on the final assembled string after whitespace cleanup, as a safety net.
208
+
209
+ #### Built-in modes
210
+
211
+ - `'end'` — keep the head, drop the tail, append the marker.
212
+ - `'start'` — drop the head, keep the tail, prepend the marker.
213
+ - `'middle'` — keep head and tail, drop the middle, insert the marker between them.
214
+
215
+ The default marker is `…[truncated N chars]`, where `N` is the number of characters removed.
216
+
217
+ #### Custom truncation functions
218
+
219
+ A function in `truncate` or `valueTruncate` fully owns the decision. It receives a `ctx` containing the resolved options plus `ctx.truncate(text, mode?)` — a delegate back into the library's built-in algorithm.
220
+
221
+ ```ts
222
+ const ai = promptCreate({
223
+ maxChars: 8_000,
224
+ marker: (removed) => `\n…[dropped ${removed} chars]`,
225
+ truncate: (text, ctx) => {
226
+ if (text.length <= ctx.maxChars) return text
227
+ // Custom rule: try middle first, fall back to end.
228
+ const mid = ctx.truncate(text, 'middle')
229
+ return mid.length <= ctx.maxChars ? mid : ctx.truncate(text, 'end')
230
+ },
231
+ })
232
+ ```
233
+
234
+ The per-value variant additionally receives the original `PromptValue`, so it can branch on type — e.g. render a giant array differently from a long string:
235
+
236
+ ```ts
237
+ const ai = promptCreate({
238
+ valueMaxChars: 1_000,
239
+ valueTruncate: (rendered, original, ctx) => {
240
+ if (Array.isArray(original) && original.length > 50) {
241
+ return ctx.truncate(rendered, 'end')
242
+ }
243
+ return ctx.truncate(rendered, 'middle')
244
+ },
245
+ })
246
+ ```
247
+
248
+ ## Composition
249
+
250
+ Because every `prompt\`...\`` call returns a plain string and accepts plain strings as values, prompts compose by ordinary interpolation:
251
+
252
+ ```ts
253
+ const persona = ai`
254
+ You are a senior reviewer. Be concise.
255
+ `
256
+
257
+ const task = ai`
258
+ Review the diff and list risks.
259
+ `
260
+
261
+ const full = ai`
262
+ ${persona}
263
+
264
+ ${task}
265
+
266
+ Diff:
267
+ ${diff}
268
+ `
269
+ ```
270
+
271
+ Conditionals are plain JS — no `when` / `if` helpers:
272
+
273
+ ```ts
274
+ ai`
275
+ ${user.isAdmin && ai`
276
+ Admin notes:
277
+ ${adminNotes}
278
+ `}
279
+ `
280
+ ```
281
+
282
+ A falsy interpolation renders as nothing and its surrounding blank lines are collapsed by the cleanup pass.
283
+
284
+ ## API surface
285
+
286
+ ```ts
287
+ export class JsonValue<T = any>
288
+ export const renderPrompt: unique symbol
289
+ export type PromptValue
290
+ export type PromptOptions
291
+ export function prompt(strings: TemplateStringsArray, ...values: PromptValue[]): string
292
+ export function promptCreate(options?: PromptOptions): typeof prompt
293
+ export const ai: typeof prompt
294
+ ```
295
+
296
+ ## Non-goals
297
+
298
+ Kept deliberately out of scope to stay simple:
299
+
300
+ - token counting (needs a model-specific tokenizer; belongs in the API client layer)
301
+ - async / `Promise` values (await before passing in)
302
+ - message arrays, roles, tools, multi-modal content (caller's responsibility)
303
+ - helper sugar like `section()`, `when()`, `list()` (use plain JS)
304
+ - markdown / XML escaping (caller's responsibility)
package/dist/index.cjs ADDED
@@ -0,0 +1,162 @@
1
+ 'use strict';
2
+
3
+ // src/prompt.ts
4
+ var JsonValue = class {
5
+ constructor(value) {
6
+ this.value = value;
7
+ }
8
+ value;
9
+ };
10
+ var renderPrompt = /* @__PURE__ */ Symbol("prompt.render");
11
+ var defaultMarker = (removed) => `\u2026[truncated ${removed} chars]`;
12
+ function resolveMarker(m, removed) {
13
+ const mk = m ?? defaultMarker;
14
+ return typeof mk === "function" ? mk(removed) : mk;
15
+ }
16
+ function builtinTruncate(text, mode, maxChars, marker) {
17
+ if (text.length <= maxChars) return text;
18
+ const estRemoved = text.length - maxChars;
19
+ const markerStr = resolveMarker(marker, estRemoved);
20
+ const budget = Math.max(0, maxChars - markerStr.length);
21
+ if (budget <= 0) return markerStr.slice(0, maxChars);
22
+ switch (mode) {
23
+ case "end":
24
+ return text.slice(0, budget) + markerStr;
25
+ case "start":
26
+ return markerStr + text.slice(text.length - budget);
27
+ case "middle": {
28
+ const head = Math.ceil(budget / 2);
29
+ const tail = budget - head;
30
+ return text.slice(0, head) + markerStr + text.slice(text.length - tail);
31
+ }
32
+ }
33
+ }
34
+ function resolveOutput(opts) {
35
+ const t = opts.truncate;
36
+ const custom = typeof t === "function" ? t : void 0;
37
+ const mode = typeof t === "string" ? t : "end";
38
+ const maxChars = opts.maxChars ?? 0;
39
+ return {
40
+ enabled: custom !== void 0 || maxChars > 0,
41
+ maxChars,
42
+ mode,
43
+ marker: opts.marker ?? defaultMarker,
44
+ custom
45
+ };
46
+ }
47
+ function resolveValue(opts) {
48
+ const t = opts.valueTruncate;
49
+ const custom = typeof t === "function" ? t : void 0;
50
+ const mode = typeof t === "string" ? t : "end";
51
+ const maxChars = opts.valueMaxChars ?? 0;
52
+ return {
53
+ enabled: custom !== void 0 || maxChars > 0,
54
+ maxChars,
55
+ mode,
56
+ marker: opts.valueMarker ?? defaultMarker,
57
+ custom
58
+ };
59
+ }
60
+ function makeCtx(r) {
61
+ return {
62
+ maxChars: r.maxChars,
63
+ mode: r.mode,
64
+ marker: r.marker,
65
+ truncate: (text, mode) => builtinTruncate(text, mode ?? r.mode, r.maxChars, r.marker)
66
+ };
67
+ }
68
+ function renderRaw(value, valueCfg) {
69
+ if (Array.isArray(value)) {
70
+ return value.filter((v) => v !== null && v !== void 0 && v !== false).map((v) => flatten(v, valueCfg)).join("\n");
71
+ }
72
+ if (value === null || value === void 0 || value === false) return "";
73
+ if (typeof value === "object") {
74
+ const r = value[renderPrompt];
75
+ if (typeof r === "function") {
76
+ return flatten(r.call(value), valueCfg);
77
+ }
78
+ let jsonValue;
79
+ if (value instanceof JsonValue) {
80
+ jsonValue = value.value;
81
+ } else if (typeof value.toJSONSchema === "function") {
82
+ jsonValue = value.toJSONSchema({ io: "input" });
83
+ } else {
84
+ jsonValue = value;
85
+ }
86
+ return "```json\n" + JSON.stringify(jsonValue, null, 2) + "\n```";
87
+ }
88
+ return String(value).trim();
89
+ }
90
+ function flatten(value, valueCfg) {
91
+ const rendered = renderRaw(value, valueCfg);
92
+ if (!valueCfg.enabled) return rendered;
93
+ const ctx = makeCtx(valueCfg);
94
+ if (valueCfg.custom) return valueCfg.custom(rendered, value, ctx);
95
+ return builtinTruncate(rendered, valueCfg.mode, valueCfg.maxChars, valueCfg.marker);
96
+ }
97
+ function measureMinIndent(strings) {
98
+ const joined = strings.join("\0");
99
+ let min = Infinity;
100
+ for (const line of joined.split("\n")) {
101
+ if (!/\S/.test(line)) continue;
102
+ const m = /^[ \t]*/.exec(line);
103
+ const ind = m ? m[0].length : 0;
104
+ if (ind < min) min = ind;
105
+ }
106
+ return min === Infinity ? 0 : min;
107
+ }
108
+ function dedentStrings(strings) {
109
+ const n = measureMinIndent(strings);
110
+ if (n === 0) return strings.slice();
111
+ const re = new RegExp(`^[ \\t]{0,${n}}`);
112
+ return strings.map((s, i) => {
113
+ const lines = s.split("\n");
114
+ return lines.map((line, j) => {
115
+ const isLineStart = j > 0 || i === 0;
116
+ return isLineStart ? line.replace(re, "") : line;
117
+ }).join("\n");
118
+ });
119
+ }
120
+ function assemble(strings, values, valueCfg) {
121
+ const out = [];
122
+ for (let i = 0; i < strings.length; i++) {
123
+ const lines = strings[i].split("\n");
124
+ const lastPrefix = lines.pop() ?? "";
125
+ for (const l of lines) out.push(l, "\n");
126
+ out.push(lastPrefix);
127
+ if (i < values.length) {
128
+ const flat = flatten(values[i], valueCfg).split("\n");
129
+ out.push(flat[0]);
130
+ for (let k = 1; k < flat.length; k++) out.push("\n", lastPrefix, flat[k]);
131
+ }
132
+ }
133
+ return out.join("");
134
+ }
135
+ function compose(strings, values, opts) {
136
+ const dedent = opts.dedent !== false;
137
+ const valueCfg = resolveValue(opts);
138
+ const outputCfg = resolveOutput(opts);
139
+ const prepared = dedent ? dedentStrings(strings) : strings.slice();
140
+ let result = assemble(prepared, values, valueCfg);
141
+ result = result.replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
142
+ if (outputCfg.enabled) {
143
+ const ctx = makeCtx(outputCfg);
144
+ result = outputCfg.custom ? outputCfg.custom(result, ctx) : builtinTruncate(result, outputCfg.mode, outputCfg.maxChars, outputCfg.marker);
145
+ }
146
+ return result;
147
+ }
148
+ function prompt(strings, ...values) {
149
+ return compose(strings, values, {});
150
+ }
151
+ function promptCreate(options = {}) {
152
+ return ((strings, ...values) => compose(strings, values, options));
153
+ }
154
+ var ai = prompt;
155
+
156
+ exports.JsonValue = JsonValue;
157
+ exports.ai = ai;
158
+ exports.prompt = prompt;
159
+ exports.promptCreate = promptCreate;
160
+ exports.renderPrompt = renderPrompt;
161
+ //# sourceMappingURL=index.cjs.map
162
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/prompt.ts"],"names":[],"mappings":";;;AAAO,IAAM,YAAN,MAA6B;AAAA,EACnC,YAA4B,KAAA,EAAU;AAAV,IAAA,IAAA,CAAA,KAAA,GAAA,KAAA;AAAA,EAAW;AAAA,EAAX,KAAA;AAC7B;AAEO,IAAM,YAAA,0BAAsB,eAAe;AA4ClD,IAAM,aAAA,GAAgC,CAAC,OAAA,KAAY,CAAA,iBAAA,EAAe,OAAO,CAAA,OAAA,CAAA;AAEzE,SAAS,aAAA,CAAc,GAA+B,OAAA,EAAyB;AAC9E,EAAA,MAAM,KAAK,CAAA,IAAK,aAAA;AAChB,EAAA,OAAO,OAAO,EAAA,KAAO,UAAA,GAAa,EAAA,CAAG,OAAO,CAAA,GAAI,EAAA;AACjD;AAEA,SAAS,eAAA,CACR,IAAA,EACA,IAAA,EACA,QAAA,EACA,MAAA,EACS;AACT,EAAA,IAAI,IAAA,CAAK,MAAA,IAAU,QAAA,EAAU,OAAO,IAAA;AACpC,EAAA,MAAM,UAAA,GAAa,KAAK,MAAA,GAAS,QAAA;AACjC,EAAA,MAAM,SAAA,GAAY,aAAA,CAAc,MAAA,EAAQ,UAAU,CAAA;AAClD,EAAA,MAAM,SAAS,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,QAAA,GAAW,UAAU,MAAM,CAAA;AACtD,EAAA,IAAI,UAAU,CAAA,EAAG,OAAO,SAAA,CAAU,KAAA,CAAM,GAAG,QAAQ,CAAA;AACnD,EAAA,QAAQ,IAAA;AAAM,IACb,KAAK,KAAA;AACJ,MAAA,OAAO,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,MAAM,CAAA,GAAI,SAAA;AAAA,IAChC,KAAK,OAAA;AACJ,MAAA,OAAO,SAAA,GAAY,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,SAAS,MAAM,CAAA;AAAA,IACnD,KAAK,QAAA,EAAU;AACd,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,CAAK,MAAA,GAAS,CAAC,CAAA;AACjC,MAAA,MAAM,OAAO,MAAA,GAAS,IAAA;AACtB,MAAA,OAAO,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,IAAI,CAAA,GAAI,YAAY,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,MAAA,GAAS,IAAI,CAAA;AAAA,IACvE;AAAA;AAEF;AAkBA,SAAS,cAAc,IAAA,EAAqC;AAC3D,EAAA,MAAM,IAAI,IAAA,CAAK,QAAA;AACf,EAAA,MAAM,MAAA,GAAS,OAAO,CAAA,KAAM,UAAA,GAAc,CAAA,GAAyB,MAAA;AACnE,EAAA,MAAM,IAAA,GAAqB,OAAO,CAAA,KAAM,QAAA,GAAW,CAAA,GAAI,KAAA;AACvD,EAAA,MAAM,QAAA,GAAW,KAAK,QAAA,IAAY,CAAA;AAClC,EAAA,OAAO;AAAA,IACN,OAAA,EAAS,MAAA,KAAW,MAAA,IAAa,QAAA,GAAW,CAAA;AAAA,IAC5C,QAAA;AAAA,IACA,IAAA;AAAA,IACA,MAAA,EAAQ,KAAK,MAAA,IAAU,aAAA;AAAA,IACvB;AAAA,GACD;AACD;AAEA,SAAS,aAAa,IAAA,EAAoC;AACzD,EAAA,MAAM,IAAI,IAAA,CAAK,aAAA;AACf,EAAA,MAAM,MAAA,GAAS,OAAO,CAAA,KAAM,UAAA,GAAc,CAAA,GAAwB,MAAA;AAClE,EAAA,MAAM,IAAA,GAAqB,OAAO,CAAA,KAAM,QAAA,GAAW,CAAA,GAAI,KAAA;AACvD,EAAA,MAAM,QAAA,GAAW,KAAK,aAAA,IAAiB,CAAA;AACvC,EAAA,OAAO;AAAA,IACN,OAAA,EAAS,MAAA,KAAW,MAAA,IAAa,QAAA,GAAW,CAAA;AAAA,IAC5C,QAAA;AAAA,IACA,IAAA;AAAA,IACA,MAAA,EAAQ,KAAK,WAAA,IAAe,aAAA;AAAA,IAC5B;AAAA,GACD;AACD;AAEA,SAAS,QAAQ,CAAA,EAAkF;AAClG,EAAA,OAAO;AAAA,IACN,UAAU,CAAA,CAAE,QAAA;AAAA,IACZ,MAAM,CAAA,CAAE,IAAA;AAAA,IACR,QAAQ,CAAA,CAAE,MAAA;AAAA,IACV,QAAA,EAAU,CAAC,IAAA,EAAM,IAAA,KAAS,eAAA,CAAgB,IAAA,EAAM,IAAA,IAAQ,CAAA,CAAE,IAAA,EAAM,CAAA,CAAE,QAAA,EAAU,CAAA,CAAE,MAAM;AAAA,GACrF;AACD;AAEA,SAAS,SAAA,CAAU,OAAoB,QAAA,EAAiC;AACvE,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACzB,IAAA,OAAO,KAAA,CACL,OAAO,CAAC,CAAA,KAAM,MAAM,IAAA,IAAQ,CAAA,KAAM,UAAa,CAAA,KAAM,KAAK,EAC1D,GAAA,CAAI,CAAC,MAAM,OAAA,CAAQ,CAAA,EAAkB,QAAQ,CAAC,CAAA,CAC9C,KAAK,IAAI,CAAA;AAAA,EACZ;AACA,EAAA,IAAI,UAAU,IAAA,IAAQ,KAAA,KAAU,MAAA,IAAa,KAAA,KAAU,OAAO,OAAO,EAAA;AACrE,EAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC9B,IAAA,MAAM,CAAA,GAAK,MAAkC,YAAY,CAAA;AACzD,IAAA,IAAI,OAAO,MAAM,UAAA,EAAY;AAC5B,MAAA,OAAO,OAAA,CAAS,CAAA,CAAwB,IAAA,CAAK,KAAK,GAAG,QAAQ,CAAA;AAAA,IAC9D;AACA,IAAA,IAAI,SAAA;AACJ,IAAA,IAAI,iBAAiB,SAAA,EAAW;AAC/B,MAAA,SAAA,GAAY,KAAA,CAAM,KAAA;AAAA,IACnB,CAAA,MAAA,IAAW,OAAQ,KAAA,CAAkB,YAAA,KAAiB,UAAA,EAAY;AACjE,MAAA,SAAA,GAAa,KAAA,CAAkB,YAAA,CAAa,EAAE,EAAA,EAAI,SAAS,CAAA;AAAA,IAC5D,CAAA,MAAO;AACN,MAAA,SAAA,GAAY,KAAA;AAAA,IACb;AACA,IAAA,OAAO,cAAc,IAAA,CAAK,SAAA,CAAU,SAAA,EAAW,IAAA,EAAM,CAAC,CAAA,GAAI,OAAA;AAAA,EAC3D;AACA,EAAA,OAAO,MAAA,CAAO,KAAK,CAAA,CAAE,IAAA,EAAK;AAC3B;AAEA,SAAS,OAAA,CAAQ,OAAoB,QAAA,EAAiC;AACrE,EAAA,MAAM,QAAA,GAAW,SAAA,CAAU,KAAA,EAAO,QAAQ,CAAA;AAC1C,EAAA,IAAI,CAAC,QAAA,CAAS,OAAA,EAAS,OAAO,QAAA;AAC9B,EAAA,MAAM,GAAA,GAAM,QAAQ,QAAQ,CAAA;AAC5B,EAAA,IAAI,SAAS,MAAA,EAAQ,OAAO,SAAS,MAAA,CAAO,QAAA,EAAU,OAAO,GAAG,CAAA;AAChE,EAAA,OAAO,gBAAgB,QAAA,EAAU,QAAA,CAAS,MAAM,QAAA,CAAS,QAAA,EAAU,SAAS,MAAM,CAAA;AACnF;AAEA,SAAS,iBAAiB,OAAA,EAAoC;AAG7D,EAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,IAAA,CAAK,IAAM,CAAA;AAClC,EAAA,IAAI,GAAA,GAAM,QAAA;AACV,EAAA,KAAA,MAAW,IAAA,IAAQ,MAAA,CAAO,KAAA,CAAM,IAAI,CAAA,EAAG;AACtC,IAAA,IAAI,CAAC,IAAA,CAAK,IAAA,CAAK,IAAI,CAAA,EAAG;AACtB,IAAA,MAAM,CAAA,GAAI,SAAA,CAAU,IAAA,CAAK,IAAI,CAAA;AAC7B,IAAA,MAAM,GAAA,GAAM,CAAA,GAAI,CAAA,CAAE,CAAC,EAAE,MAAA,GAAS,CAAA;AAC9B,IAAA,IAAI,GAAA,GAAM,KAAK,GAAA,GAAM,GAAA;AAAA,EACtB;AACA,EAAA,OAAO,GAAA,KAAQ,WAAW,CAAA,GAAI,GAAA;AAC/B;AAEA,SAAS,cAAc,OAAA,EAAsC;AAC5D,EAAA,MAAM,CAAA,GAAI,iBAAiB,OAAO,CAAA;AAClC,EAAA,IAAI,CAAA,KAAM,CAAA,EAAG,OAAO,OAAA,CAAQ,KAAA,EAAM;AAClC,EAAA,MAAM,EAAA,GAAK,IAAI,MAAA,CAAO,CAAA,UAAA,EAAa,CAAC,CAAA,CAAA,CAAG,CAAA;AACvC,EAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,EAAG,CAAA,KAAM;AAC5B,IAAA,MAAM,KAAA,GAAQ,CAAA,CAAE,KAAA,CAAM,IAAI,CAAA;AAC1B,IAAA,OAAO,KAAA,CACL,GAAA,CAAI,CAAC,IAAA,EAAM,CAAA,KAAM;AACjB,MAAA,MAAM,WAAA,GAAc,CAAA,GAAI,CAAA,IAAK,CAAA,KAAM,CAAA;AACnC,MAAA,OAAO,WAAA,GAAc,IAAA,CAAK,OAAA,CAAQ,EAAA,EAAI,EAAE,CAAA,GAAI,IAAA;AAAA,IAC7C,CAAC,CAAA,CACA,IAAA,CAAK,IAAI,CAAA;AAAA,EACZ,CAAC,CAAA;AACF;AAEA,SAAS,QAAA,CACR,OAAA,EACA,MAAA,EACA,QAAA,EACS;AACT,EAAA,MAAM,MAAgB,EAAC;AACvB,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,OAAA,CAAQ,QAAQ,CAAA,EAAA,EAAK;AACxC,IAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,CAAC,CAAA,CAAG,MAAM,IAAI,CAAA;AACpC,IAAA,MAAM,UAAA,GAAa,KAAA,CAAM,GAAA,EAAI,IAAK,EAAA;AAClC,IAAA,KAAA,MAAW,CAAA,IAAK,KAAA,EAAO,GAAA,CAAI,IAAA,CAAK,GAAG,IAAI,CAAA;AACvC,IAAA,GAAA,CAAI,KAAK,UAAU,CAAA;AACnB,IAAA,IAAI,CAAA,GAAI,OAAO,MAAA,EAAQ;AACtB,MAAA,MAAM,IAAA,GAAO,QAAQ,MAAA,CAAO,CAAC,GAAI,QAAQ,CAAA,CAAE,MAAM,IAAI,CAAA;AACrD,MAAA,GAAA,CAAI,IAAA,CAAK,IAAA,CAAK,CAAC,CAAE,CAAA;AACjB,MAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,IAAA,CAAK,MAAA,EAAQ,CAAA,EAAA,EAAK,GAAA,CAAI,IAAA,CAAK,IAAA,EAAM,UAAA,EAAY,IAAA,CAAK,CAAC,CAAE,CAAA;AAAA,IAC1E;AAAA,EACD;AACA,EAAA,OAAO,GAAA,CAAI,KAAK,EAAE,CAAA;AACnB;AAEA,SAAS,OAAA,CACR,OAAA,EACA,MAAA,EACA,IAAA,EACS;AACT,EAAA,MAAM,MAAA,GAAS,KAAK,MAAA,KAAW,KAAA;AAC/B,EAAA,MAAM,QAAA,GAAW,aAAa,IAAI,CAAA;AAClC,EAAA,MAAM,SAAA,GAAY,cAAc,IAAI,CAAA;AACpC,EAAA,MAAM,WAAW,MAAA,GAAS,aAAA,CAAc,OAAO,CAAA,GAAI,QAAQ,KAAA,EAAM;AACjE,EAAA,IAAI,MAAA,GAAS,QAAA,CAAS,QAAA,EAAU,MAAA,EAAQ,QAAQ,CAAA;AAChD,EAAA,MAAA,GAAS,MAAA,CAAO,QAAQ,WAAA,EAAa,IAAI,EAAE,OAAA,CAAQ,SAAA,EAAW,MAAM,CAAA,CAAE,IAAA,EAAK;AAC3E,EAAA,IAAI,UAAU,OAAA,EAAS;AACtB,IAAA,MAAM,GAAA,GAAM,QAAQ,SAAS,CAAA;AAC7B,IAAA,MAAA,GAAS,SAAA,CAAU,MAAA,GAChB,SAAA,CAAU,MAAA,CAAO,QAAQ,GAAG,CAAA,GAC5B,eAAA,CAAgB,MAAA,EAAQ,SAAA,CAAU,IAAA,EAAM,SAAA,CAAU,QAAA,EAAU,UAAU,MAAM,CAAA;AAAA,EAChF;AACA,EAAA,OAAO,MAAA;AACR;AAQO,SAAS,MAAA,CAAO,YAAkC,MAAA,EAA+B;AACvF,EAAA,OAAO,OAAA,CAAQ,OAAA,EAAS,MAAA,EAAQ,EAAE,CAAA;AACnC;AAMO,SAAS,YAAA,CAAa,OAAA,GAAyB,EAAC,EAAkB;AACxE,EAAA,QAAQ,CAAC,OAAA,EAAA,GAAkC,MAAA,KAC1C,OAAA,CAAQ,OAAA,EAAS,QAAQ,OAAO,CAAA;AAClC;AAEO,IAAM,EAAA,GAAoB","file":"index.cjs","sourcesContent":["export class JsonValue<T = unknown> {\n\tconstructor(public readonly value: T) {}\n}\n\nexport const renderPrompt = Symbol('prompt.render')\n\ntype ZodLike = { toJSONSchema(input?: { io: 'input' | 'output' }): object }\ntype Renderable = { [renderPrompt](): PromptValue }\n\ntype Primitive =\n\t| string\n\t| number\n\t| boolean\n\t| null\n\t| undefined\n\t| object\n\t| JsonValue\n\t| ZodLike\n\t| Renderable\n\nexport type PromptValue = Primitive | readonly PromptValue[]\n\nexport type TruncateMode = 'end' | 'start' | 'middle'\nexport type TruncateMarker = string | ((removedChars: number) => string)\n\nexport interface TruncateCtx {\n\tmaxChars: number\n\tmode: TruncateMode\n\tmarker: TruncateMarker\n\ttruncate: (text: string, mode?: TruncateMode) => string\n}\n\nexport type OutputTruncateFn = (text: string, ctx: TruncateCtx) => string\nexport type ValueTruncateFn = (rendered: string, original: PromptValue, ctx: TruncateCtx) => string\n\ntype OutputTruncate =\n\t| { maxChars: number; truncate?: TruncateMode; marker?: TruncateMarker }\n\t| { truncate: OutputTruncateFn; maxChars?: number; marker?: TruncateMarker }\n\ntype ValueTruncate =\n\t| { valueMaxChars: number; valueTruncate?: TruncateMode; valueMarker?: TruncateMarker }\n\t| { valueTruncate: ValueTruncateFn; valueMaxChars?: number; valueMarker?: TruncateMarker }\n\nexport type PromptOptions =\n\t& { dedent?: boolean }\n\t& Partial<OutputTruncate>\n\t& Partial<ValueTruncate>\n\nconst defaultMarker: TruncateMarker = (removed) => `…[truncated ${removed} chars]`\n\nfunction resolveMarker(m: TruncateMarker | undefined, removed: number): string {\n\tconst mk = m ?? defaultMarker\n\treturn typeof mk === 'function' ? mk(removed) : mk\n}\n\nfunction builtinTruncate(\n\ttext: string,\n\tmode: TruncateMode,\n\tmaxChars: number,\n\tmarker: TruncateMarker,\n): string {\n\tif (text.length <= maxChars) return text\n\tconst estRemoved = text.length - maxChars\n\tconst markerStr = resolveMarker(marker, estRemoved)\n\tconst budget = Math.max(0, maxChars - markerStr.length)\n\tif (budget <= 0) return markerStr.slice(0, maxChars)\n\tswitch (mode) {\n\t\tcase 'end':\n\t\t\treturn text.slice(0, budget) + markerStr\n\t\tcase 'start':\n\t\t\treturn markerStr + text.slice(text.length - budget)\n\t\tcase 'middle': {\n\t\t\tconst head = Math.ceil(budget / 2)\n\t\t\tconst tail = budget - head\n\t\t\treturn text.slice(0, head) + markerStr + text.slice(text.length - tail)\n\t\t}\n\t}\n}\n\ninterface ResolvedOutput {\n\tenabled: boolean\n\tmaxChars: number\n\tmode: TruncateMode\n\tmarker: TruncateMarker\n\tcustom?: OutputTruncateFn\n}\n\ninterface ResolvedValue {\n\tenabled: boolean\n\tmaxChars: number\n\tmode: TruncateMode\n\tmarker: TruncateMarker\n\tcustom?: ValueTruncateFn\n}\n\nfunction resolveOutput(opts: PromptOptions): ResolvedOutput {\n\tconst t = opts.truncate\n\tconst custom = typeof t === 'function' ? (t as OutputTruncateFn) : undefined\n\tconst mode: TruncateMode = typeof t === 'string' ? t : 'end'\n\tconst maxChars = opts.maxChars ?? 0\n\treturn {\n\t\tenabled: custom !== undefined || maxChars > 0,\n\t\tmaxChars,\n\t\tmode,\n\t\tmarker: opts.marker ?? defaultMarker,\n\t\tcustom,\n\t}\n}\n\nfunction resolveValue(opts: PromptOptions): ResolvedValue {\n\tconst t = opts.valueTruncate\n\tconst custom = typeof t === 'function' ? (t as ValueTruncateFn) : undefined\n\tconst mode: TruncateMode = typeof t === 'string' ? t : 'end'\n\tconst maxChars = opts.valueMaxChars ?? 0\n\treturn {\n\t\tenabled: custom !== undefined || maxChars > 0,\n\t\tmaxChars,\n\t\tmode,\n\t\tmarker: opts.valueMarker ?? defaultMarker,\n\t\tcustom,\n\t}\n}\n\nfunction makeCtx(r: { maxChars: number; mode: TruncateMode; marker: TruncateMarker }): TruncateCtx {\n\treturn {\n\t\tmaxChars: r.maxChars,\n\t\tmode: r.mode,\n\t\tmarker: r.marker,\n\t\ttruncate: (text, mode) => builtinTruncate(text, mode ?? r.mode, r.maxChars, r.marker),\n\t}\n}\n\nfunction renderRaw(value: PromptValue, valueCfg: ResolvedValue): string {\n\tif (Array.isArray(value)) {\n\t\treturn value\n\t\t\t.filter((v) => v !== null && v !== undefined && v !== false)\n\t\t\t.map((v) => flatten(v as PromptValue, valueCfg))\n\t\t\t.join('\\n')\n\t}\n\tif (value === null || value === undefined || value === false) return ''\n\tif (typeof value === 'object') {\n\t\tconst r = (value as Record<symbol, unknown>)[renderPrompt]\n\t\tif (typeof r === 'function') {\n\t\t\treturn flatten((r as () => PromptValue).call(value), valueCfg)\n\t\t}\n\t\tlet jsonValue: unknown\n\t\tif (value instanceof JsonValue) {\n\t\t\tjsonValue = value.value\n\t\t} else if (typeof (value as ZodLike).toJSONSchema === 'function') {\n\t\t\tjsonValue = (value as ZodLike).toJSONSchema({ io: 'input' })\n\t\t} else {\n\t\t\tjsonValue = value\n\t\t}\n\t\treturn '```json\\n' + JSON.stringify(jsonValue, null, 2) + '\\n```'\n\t}\n\treturn String(value).trim()\n}\n\nfunction flatten(value: PromptValue, valueCfg: ResolvedValue): string {\n\tconst rendered = renderRaw(value, valueCfg)\n\tif (!valueCfg.enabled) return rendered\n\tconst ctx = makeCtx(valueCfg)\n\tif (valueCfg.custom) return valueCfg.custom(rendered, value, ctx)\n\treturn builtinTruncate(rendered, valueCfg.mode, valueCfg.maxChars, valueCfg.marker)\n}\n\nfunction measureMinIndent(strings: readonly string[]): number {\n\t// Placeholder for interpolation slots so lines that contain only an indent\n\t// + a ${value} still count as content lines for indent measurement.\n\tconst joined = strings.join('\\x00')\n\tlet min = Infinity\n\tfor (const line of joined.split('\\n')) {\n\t\tif (!/\\S/.test(line)) continue\n\t\tconst m = /^[ \\t]*/.exec(line)\n\t\tconst ind = m ? m[0].length : 0\n\t\tif (ind < min) min = ind\n\t}\n\treturn min === Infinity ? 0 : min\n}\n\nfunction dedentStrings(strings: readonly string[]): string[] {\n\tconst n = measureMinIndent(strings)\n\tif (n === 0) return strings.slice()\n\tconst re = new RegExp(`^[ \\\\t]{0,${n}}`)\n\treturn strings.map((s, i) => {\n\t\tconst lines = s.split('\\n')\n\t\treturn lines\n\t\t\t.map((line, j) => {\n\t\t\t\tconst isLineStart = j > 0 || i === 0\n\t\t\t\treturn isLineStart ? line.replace(re, '') : line\n\t\t\t})\n\t\t\t.join('\\n')\n\t})\n}\n\nfunction assemble(\n\tstrings: readonly string[],\n\tvalues: readonly PromptValue[],\n\tvalueCfg: ResolvedValue,\n): string {\n\tconst out: string[] = []\n\tfor (let i = 0; i < strings.length; i++) {\n\t\tconst lines = strings[i]!.split('\\n')\n\t\tconst lastPrefix = lines.pop() ?? ''\n\t\tfor (const l of lines) out.push(l, '\\n')\n\t\tout.push(lastPrefix)\n\t\tif (i < values.length) {\n\t\t\tconst flat = flatten(values[i]!, valueCfg).split('\\n')\n\t\t\tout.push(flat[0]!)\n\t\t\tfor (let k = 1; k < flat.length; k++) out.push('\\n', lastPrefix, flat[k]!)\n\t\t}\n\t}\n\treturn out.join('')\n}\n\nfunction compose(\n\tstrings: readonly string[],\n\tvalues: readonly PromptValue[],\n\topts: PromptOptions,\n): string {\n\tconst dedent = opts.dedent !== false\n\tconst valueCfg = resolveValue(opts)\n\tconst outputCfg = resolveOutput(opts)\n\tconst prepared = dedent ? dedentStrings(strings) : strings.slice()\n\tlet result = assemble(prepared, values, valueCfg)\n\tresult = result.replace(/[ \\t]+\\n/g, '\\n').replace(/\\n{3,}/g, '\\n\\n').trim()\n\tif (outputCfg.enabled) {\n\t\tconst ctx = makeCtx(outputCfg)\n\t\tresult = outputCfg.custom\n\t\t\t? outputCfg.custom(result, ctx)\n\t\t\t: builtinTruncate(result, outputCfg.mode, outputCfg.maxChars, outputCfg.marker)\n\t}\n\treturn result\n}\n\n/**\n * Tagged template that assembles a clean LLM prompt string.\n *\n * Preserves the indentation/prefix of the line each `${value}` sits on so\n * lists, block quotes, and multi-line interpolations render correctly.\n */\nexport function prompt(strings: TemplateStringsArray, ...values: PromptValue[]): string {\n\treturn compose(strings, values, {})\n}\n\n/**\n * Returns a configured `prompt` tag. With no arguments it behaves identically\n * to bare {@link prompt}.\n */\nexport function promptCreate(options: PromptOptions = {}): typeof prompt {\n\treturn ((strings: TemplateStringsArray, ...values: PromptValue[]) =>\n\t\tcompose(strings, values, options)) as typeof prompt\n}\n\nexport const ai: typeof prompt = prompt\n"]}
@@ -0,0 +1,61 @@
1
+ declare class JsonValue<T = unknown> {
2
+ readonly value: T;
3
+ constructor(value: T);
4
+ }
5
+ declare const renderPrompt: unique symbol;
6
+ type ZodLike = {
7
+ toJSONSchema(input?: {
8
+ io: 'input' | 'output';
9
+ }): object;
10
+ };
11
+ type Renderable = {
12
+ [renderPrompt](): PromptValue;
13
+ };
14
+ type Primitive = string | number | boolean | null | undefined | object | JsonValue | ZodLike | Renderable;
15
+ type PromptValue = Primitive | readonly PromptValue[];
16
+ type TruncateMode = 'end' | 'start' | 'middle';
17
+ type TruncateMarker = string | ((removedChars: number) => string);
18
+ interface TruncateCtx {
19
+ maxChars: number;
20
+ mode: TruncateMode;
21
+ marker: TruncateMarker;
22
+ truncate: (text: string, mode?: TruncateMode) => string;
23
+ }
24
+ type OutputTruncateFn = (text: string, ctx: TruncateCtx) => string;
25
+ type ValueTruncateFn = (rendered: string, original: PromptValue, ctx: TruncateCtx) => string;
26
+ type OutputTruncate = {
27
+ maxChars: number;
28
+ truncate?: TruncateMode;
29
+ marker?: TruncateMarker;
30
+ } | {
31
+ truncate: OutputTruncateFn;
32
+ maxChars?: number;
33
+ marker?: TruncateMarker;
34
+ };
35
+ type ValueTruncate = {
36
+ valueMaxChars: number;
37
+ valueTruncate?: TruncateMode;
38
+ valueMarker?: TruncateMarker;
39
+ } | {
40
+ valueTruncate: ValueTruncateFn;
41
+ valueMaxChars?: number;
42
+ valueMarker?: TruncateMarker;
43
+ };
44
+ type PromptOptions = {
45
+ dedent?: boolean;
46
+ } & Partial<OutputTruncate> & Partial<ValueTruncate>;
47
+ /**
48
+ * Tagged template that assembles a clean LLM prompt string.
49
+ *
50
+ * Preserves the indentation/prefix of the line each `${value}` sits on so
51
+ * lists, block quotes, and multi-line interpolations render correctly.
52
+ */
53
+ declare function prompt(strings: TemplateStringsArray, ...values: PromptValue[]): string;
54
+ /**
55
+ * Returns a configured `prompt` tag. With no arguments it behaves identically
56
+ * to bare {@link prompt}.
57
+ */
58
+ declare function promptCreate(options?: PromptOptions): typeof prompt;
59
+ declare const ai: typeof prompt;
60
+
61
+ export { JsonValue, type OutputTruncateFn, type PromptOptions, type PromptValue, type TruncateCtx, type TruncateMarker, type TruncateMode, type ValueTruncateFn, ai, prompt, promptCreate, renderPrompt };
@@ -0,0 +1,61 @@
1
+ declare class JsonValue<T = unknown> {
2
+ readonly value: T;
3
+ constructor(value: T);
4
+ }
5
+ declare const renderPrompt: unique symbol;
6
+ type ZodLike = {
7
+ toJSONSchema(input?: {
8
+ io: 'input' | 'output';
9
+ }): object;
10
+ };
11
+ type Renderable = {
12
+ [renderPrompt](): PromptValue;
13
+ };
14
+ type Primitive = string | number | boolean | null | undefined | object | JsonValue | ZodLike | Renderable;
15
+ type PromptValue = Primitive | readonly PromptValue[];
16
+ type TruncateMode = 'end' | 'start' | 'middle';
17
+ type TruncateMarker = string | ((removedChars: number) => string);
18
+ interface TruncateCtx {
19
+ maxChars: number;
20
+ mode: TruncateMode;
21
+ marker: TruncateMarker;
22
+ truncate: (text: string, mode?: TruncateMode) => string;
23
+ }
24
+ type OutputTruncateFn = (text: string, ctx: TruncateCtx) => string;
25
+ type ValueTruncateFn = (rendered: string, original: PromptValue, ctx: TruncateCtx) => string;
26
+ type OutputTruncate = {
27
+ maxChars: number;
28
+ truncate?: TruncateMode;
29
+ marker?: TruncateMarker;
30
+ } | {
31
+ truncate: OutputTruncateFn;
32
+ maxChars?: number;
33
+ marker?: TruncateMarker;
34
+ };
35
+ type ValueTruncate = {
36
+ valueMaxChars: number;
37
+ valueTruncate?: TruncateMode;
38
+ valueMarker?: TruncateMarker;
39
+ } | {
40
+ valueTruncate: ValueTruncateFn;
41
+ valueMaxChars?: number;
42
+ valueMarker?: TruncateMarker;
43
+ };
44
+ type PromptOptions = {
45
+ dedent?: boolean;
46
+ } & Partial<OutputTruncate> & Partial<ValueTruncate>;
47
+ /**
48
+ * Tagged template that assembles a clean LLM prompt string.
49
+ *
50
+ * Preserves the indentation/prefix of the line each `${value}` sits on so
51
+ * lists, block quotes, and multi-line interpolations render correctly.
52
+ */
53
+ declare function prompt(strings: TemplateStringsArray, ...values: PromptValue[]): string;
54
+ /**
55
+ * Returns a configured `prompt` tag. With no arguments it behaves identically
56
+ * to bare {@link prompt}.
57
+ */
58
+ declare function promptCreate(options?: PromptOptions): typeof prompt;
59
+ declare const ai: typeof prompt;
60
+
61
+ export { JsonValue, type OutputTruncateFn, type PromptOptions, type PromptValue, type TruncateCtx, type TruncateMarker, type TruncateMode, type ValueTruncateFn, ai, prompt, promptCreate, renderPrompt };
package/dist/index.js ADDED
@@ -0,0 +1,156 @@
1
+ // src/prompt.ts
2
+ var JsonValue = class {
3
+ constructor(value) {
4
+ this.value = value;
5
+ }
6
+ value;
7
+ };
8
+ var renderPrompt = /* @__PURE__ */ Symbol("prompt.render");
9
+ var defaultMarker = (removed) => `\u2026[truncated ${removed} chars]`;
10
+ function resolveMarker(m, removed) {
11
+ const mk = m ?? defaultMarker;
12
+ return typeof mk === "function" ? mk(removed) : mk;
13
+ }
14
+ function builtinTruncate(text, mode, maxChars, marker) {
15
+ if (text.length <= maxChars) return text;
16
+ const estRemoved = text.length - maxChars;
17
+ const markerStr = resolveMarker(marker, estRemoved);
18
+ const budget = Math.max(0, maxChars - markerStr.length);
19
+ if (budget <= 0) return markerStr.slice(0, maxChars);
20
+ switch (mode) {
21
+ case "end":
22
+ return text.slice(0, budget) + markerStr;
23
+ case "start":
24
+ return markerStr + text.slice(text.length - budget);
25
+ case "middle": {
26
+ const head = Math.ceil(budget / 2);
27
+ const tail = budget - head;
28
+ return text.slice(0, head) + markerStr + text.slice(text.length - tail);
29
+ }
30
+ }
31
+ }
32
+ function resolveOutput(opts) {
33
+ const t = opts.truncate;
34
+ const custom = typeof t === "function" ? t : void 0;
35
+ const mode = typeof t === "string" ? t : "end";
36
+ const maxChars = opts.maxChars ?? 0;
37
+ return {
38
+ enabled: custom !== void 0 || maxChars > 0,
39
+ maxChars,
40
+ mode,
41
+ marker: opts.marker ?? defaultMarker,
42
+ custom
43
+ };
44
+ }
45
+ function resolveValue(opts) {
46
+ const t = opts.valueTruncate;
47
+ const custom = typeof t === "function" ? t : void 0;
48
+ const mode = typeof t === "string" ? t : "end";
49
+ const maxChars = opts.valueMaxChars ?? 0;
50
+ return {
51
+ enabled: custom !== void 0 || maxChars > 0,
52
+ maxChars,
53
+ mode,
54
+ marker: opts.valueMarker ?? defaultMarker,
55
+ custom
56
+ };
57
+ }
58
+ function makeCtx(r) {
59
+ return {
60
+ maxChars: r.maxChars,
61
+ mode: r.mode,
62
+ marker: r.marker,
63
+ truncate: (text, mode) => builtinTruncate(text, mode ?? r.mode, r.maxChars, r.marker)
64
+ };
65
+ }
66
+ function renderRaw(value, valueCfg) {
67
+ if (Array.isArray(value)) {
68
+ return value.filter((v) => v !== null && v !== void 0 && v !== false).map((v) => flatten(v, valueCfg)).join("\n");
69
+ }
70
+ if (value === null || value === void 0 || value === false) return "";
71
+ if (typeof value === "object") {
72
+ const r = value[renderPrompt];
73
+ if (typeof r === "function") {
74
+ return flatten(r.call(value), valueCfg);
75
+ }
76
+ let jsonValue;
77
+ if (value instanceof JsonValue) {
78
+ jsonValue = value.value;
79
+ } else if (typeof value.toJSONSchema === "function") {
80
+ jsonValue = value.toJSONSchema({ io: "input" });
81
+ } else {
82
+ jsonValue = value;
83
+ }
84
+ return "```json\n" + JSON.stringify(jsonValue, null, 2) + "\n```";
85
+ }
86
+ return String(value).trim();
87
+ }
88
+ function flatten(value, valueCfg) {
89
+ const rendered = renderRaw(value, valueCfg);
90
+ if (!valueCfg.enabled) return rendered;
91
+ const ctx = makeCtx(valueCfg);
92
+ if (valueCfg.custom) return valueCfg.custom(rendered, value, ctx);
93
+ return builtinTruncate(rendered, valueCfg.mode, valueCfg.maxChars, valueCfg.marker);
94
+ }
95
+ function measureMinIndent(strings) {
96
+ const joined = strings.join("\0");
97
+ let min = Infinity;
98
+ for (const line of joined.split("\n")) {
99
+ if (!/\S/.test(line)) continue;
100
+ const m = /^[ \t]*/.exec(line);
101
+ const ind = m ? m[0].length : 0;
102
+ if (ind < min) min = ind;
103
+ }
104
+ return min === Infinity ? 0 : min;
105
+ }
106
+ function dedentStrings(strings) {
107
+ const n = measureMinIndent(strings);
108
+ if (n === 0) return strings.slice();
109
+ const re = new RegExp(`^[ \\t]{0,${n}}`);
110
+ return strings.map((s, i) => {
111
+ const lines = s.split("\n");
112
+ return lines.map((line, j) => {
113
+ const isLineStart = j > 0 || i === 0;
114
+ return isLineStart ? line.replace(re, "") : line;
115
+ }).join("\n");
116
+ });
117
+ }
118
+ function assemble(strings, values, valueCfg) {
119
+ const out = [];
120
+ for (let i = 0; i < strings.length; i++) {
121
+ const lines = strings[i].split("\n");
122
+ const lastPrefix = lines.pop() ?? "";
123
+ for (const l of lines) out.push(l, "\n");
124
+ out.push(lastPrefix);
125
+ if (i < values.length) {
126
+ const flat = flatten(values[i], valueCfg).split("\n");
127
+ out.push(flat[0]);
128
+ for (let k = 1; k < flat.length; k++) out.push("\n", lastPrefix, flat[k]);
129
+ }
130
+ }
131
+ return out.join("");
132
+ }
133
+ function compose(strings, values, opts) {
134
+ const dedent = opts.dedent !== false;
135
+ const valueCfg = resolveValue(opts);
136
+ const outputCfg = resolveOutput(opts);
137
+ const prepared = dedent ? dedentStrings(strings) : strings.slice();
138
+ let result = assemble(prepared, values, valueCfg);
139
+ result = result.replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
140
+ if (outputCfg.enabled) {
141
+ const ctx = makeCtx(outputCfg);
142
+ result = outputCfg.custom ? outputCfg.custom(result, ctx) : builtinTruncate(result, outputCfg.mode, outputCfg.maxChars, outputCfg.marker);
143
+ }
144
+ return result;
145
+ }
146
+ function prompt(strings, ...values) {
147
+ return compose(strings, values, {});
148
+ }
149
+ function promptCreate(options = {}) {
150
+ return ((strings, ...values) => compose(strings, values, options));
151
+ }
152
+ var ai = prompt;
153
+
154
+ export { JsonValue, ai, prompt, promptCreate, renderPrompt };
155
+ //# sourceMappingURL=index.js.map
156
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/prompt.ts"],"names":[],"mappings":";AAAO,IAAM,YAAN,MAA6B;AAAA,EACnC,YAA4B,KAAA,EAAU;AAAV,IAAA,IAAA,CAAA,KAAA,GAAA,KAAA;AAAA,EAAW;AAAA,EAAX,KAAA;AAC7B;AAEO,IAAM,YAAA,0BAAsB,eAAe;AA4ClD,IAAM,aAAA,GAAgC,CAAC,OAAA,KAAY,CAAA,iBAAA,EAAe,OAAO,CAAA,OAAA,CAAA;AAEzE,SAAS,aAAA,CAAc,GAA+B,OAAA,EAAyB;AAC9E,EAAA,MAAM,KAAK,CAAA,IAAK,aAAA;AAChB,EAAA,OAAO,OAAO,EAAA,KAAO,UAAA,GAAa,EAAA,CAAG,OAAO,CAAA,GAAI,EAAA;AACjD;AAEA,SAAS,eAAA,CACR,IAAA,EACA,IAAA,EACA,QAAA,EACA,MAAA,EACS;AACT,EAAA,IAAI,IAAA,CAAK,MAAA,IAAU,QAAA,EAAU,OAAO,IAAA;AACpC,EAAA,MAAM,UAAA,GAAa,KAAK,MAAA,GAAS,QAAA;AACjC,EAAA,MAAM,SAAA,GAAY,aAAA,CAAc,MAAA,EAAQ,UAAU,CAAA;AAClD,EAAA,MAAM,SAAS,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,QAAA,GAAW,UAAU,MAAM,CAAA;AACtD,EAAA,IAAI,UAAU,CAAA,EAAG,OAAO,SAAA,CAAU,KAAA,CAAM,GAAG,QAAQ,CAAA;AACnD,EAAA,QAAQ,IAAA;AAAM,IACb,KAAK,KAAA;AACJ,MAAA,OAAO,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,MAAM,CAAA,GAAI,SAAA;AAAA,IAChC,KAAK,OAAA;AACJ,MAAA,OAAO,SAAA,GAAY,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,SAAS,MAAM,CAAA;AAAA,IACnD,KAAK,QAAA,EAAU;AACd,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,CAAK,MAAA,GAAS,CAAC,CAAA;AACjC,MAAA,MAAM,OAAO,MAAA,GAAS,IAAA;AACtB,MAAA,OAAO,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,IAAI,CAAA,GAAI,YAAY,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,MAAA,GAAS,IAAI,CAAA;AAAA,IACvE;AAAA;AAEF;AAkBA,SAAS,cAAc,IAAA,EAAqC;AAC3D,EAAA,MAAM,IAAI,IAAA,CAAK,QAAA;AACf,EAAA,MAAM,MAAA,GAAS,OAAO,CAAA,KAAM,UAAA,GAAc,CAAA,GAAyB,MAAA;AACnE,EAAA,MAAM,IAAA,GAAqB,OAAO,CAAA,KAAM,QAAA,GAAW,CAAA,GAAI,KAAA;AACvD,EAAA,MAAM,QAAA,GAAW,KAAK,QAAA,IAAY,CAAA;AAClC,EAAA,OAAO;AAAA,IACN,OAAA,EAAS,MAAA,KAAW,MAAA,IAAa,QAAA,GAAW,CAAA;AAAA,IAC5C,QAAA;AAAA,IACA,IAAA;AAAA,IACA,MAAA,EAAQ,KAAK,MAAA,IAAU,aAAA;AAAA,IACvB;AAAA,GACD;AACD;AAEA,SAAS,aAAa,IAAA,EAAoC;AACzD,EAAA,MAAM,IAAI,IAAA,CAAK,aAAA;AACf,EAAA,MAAM,MAAA,GAAS,OAAO,CAAA,KAAM,UAAA,GAAc,CAAA,GAAwB,MAAA;AAClE,EAAA,MAAM,IAAA,GAAqB,OAAO,CAAA,KAAM,QAAA,GAAW,CAAA,GAAI,KAAA;AACvD,EAAA,MAAM,QAAA,GAAW,KAAK,aAAA,IAAiB,CAAA;AACvC,EAAA,OAAO;AAAA,IACN,OAAA,EAAS,MAAA,KAAW,MAAA,IAAa,QAAA,GAAW,CAAA;AAAA,IAC5C,QAAA;AAAA,IACA,IAAA;AAAA,IACA,MAAA,EAAQ,KAAK,WAAA,IAAe,aAAA;AAAA,IAC5B;AAAA,GACD;AACD;AAEA,SAAS,QAAQ,CAAA,EAAkF;AAClG,EAAA,OAAO;AAAA,IACN,UAAU,CAAA,CAAE,QAAA;AAAA,IACZ,MAAM,CAAA,CAAE,IAAA;AAAA,IACR,QAAQ,CAAA,CAAE,MAAA;AAAA,IACV,QAAA,EAAU,CAAC,IAAA,EAAM,IAAA,KAAS,eAAA,CAAgB,IAAA,EAAM,IAAA,IAAQ,CAAA,CAAE,IAAA,EAAM,CAAA,CAAE,QAAA,EAAU,CAAA,CAAE,MAAM;AAAA,GACrF;AACD;AAEA,SAAS,SAAA,CAAU,OAAoB,QAAA,EAAiC;AACvE,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACzB,IAAA,OAAO,KAAA,CACL,OAAO,CAAC,CAAA,KAAM,MAAM,IAAA,IAAQ,CAAA,KAAM,UAAa,CAAA,KAAM,KAAK,EAC1D,GAAA,CAAI,CAAC,MAAM,OAAA,CAAQ,CAAA,EAAkB,QAAQ,CAAC,CAAA,CAC9C,KAAK,IAAI,CAAA;AAAA,EACZ;AACA,EAAA,IAAI,UAAU,IAAA,IAAQ,KAAA,KAAU,MAAA,IAAa,KAAA,KAAU,OAAO,OAAO,EAAA;AACrE,EAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC9B,IAAA,MAAM,CAAA,GAAK,MAAkC,YAAY,CAAA;AACzD,IAAA,IAAI,OAAO,MAAM,UAAA,EAAY;AAC5B,MAAA,OAAO,OAAA,CAAS,CAAA,CAAwB,IAAA,CAAK,KAAK,GAAG,QAAQ,CAAA;AAAA,IAC9D;AACA,IAAA,IAAI,SAAA;AACJ,IAAA,IAAI,iBAAiB,SAAA,EAAW;AAC/B,MAAA,SAAA,GAAY,KAAA,CAAM,KAAA;AAAA,IACnB,CAAA,MAAA,IAAW,OAAQ,KAAA,CAAkB,YAAA,KAAiB,UAAA,EAAY;AACjE,MAAA,SAAA,GAAa,KAAA,CAAkB,YAAA,CAAa,EAAE,EAAA,EAAI,SAAS,CAAA;AAAA,IAC5D,CAAA,MAAO;AACN,MAAA,SAAA,GAAY,KAAA;AAAA,IACb;AACA,IAAA,OAAO,cAAc,IAAA,CAAK,SAAA,CAAU,SAAA,EAAW,IAAA,EAAM,CAAC,CAAA,GAAI,OAAA;AAAA,EAC3D;AACA,EAAA,OAAO,MAAA,CAAO,KAAK,CAAA,CAAE,IAAA,EAAK;AAC3B;AAEA,SAAS,OAAA,CAAQ,OAAoB,QAAA,EAAiC;AACrE,EAAA,MAAM,QAAA,GAAW,SAAA,CAAU,KAAA,EAAO,QAAQ,CAAA;AAC1C,EAAA,IAAI,CAAC,QAAA,CAAS,OAAA,EAAS,OAAO,QAAA;AAC9B,EAAA,MAAM,GAAA,GAAM,QAAQ,QAAQ,CAAA;AAC5B,EAAA,IAAI,SAAS,MAAA,EAAQ,OAAO,SAAS,MAAA,CAAO,QAAA,EAAU,OAAO,GAAG,CAAA;AAChE,EAAA,OAAO,gBAAgB,QAAA,EAAU,QAAA,CAAS,MAAM,QAAA,CAAS,QAAA,EAAU,SAAS,MAAM,CAAA;AACnF;AAEA,SAAS,iBAAiB,OAAA,EAAoC;AAG7D,EAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,IAAA,CAAK,IAAM,CAAA;AAClC,EAAA,IAAI,GAAA,GAAM,QAAA;AACV,EAAA,KAAA,MAAW,IAAA,IAAQ,MAAA,CAAO,KAAA,CAAM,IAAI,CAAA,EAAG;AACtC,IAAA,IAAI,CAAC,IAAA,CAAK,IAAA,CAAK,IAAI,CAAA,EAAG;AACtB,IAAA,MAAM,CAAA,GAAI,SAAA,CAAU,IAAA,CAAK,IAAI,CAAA;AAC7B,IAAA,MAAM,GAAA,GAAM,CAAA,GAAI,CAAA,CAAE,CAAC,EAAE,MAAA,GAAS,CAAA;AAC9B,IAAA,IAAI,GAAA,GAAM,KAAK,GAAA,GAAM,GAAA;AAAA,EACtB;AACA,EAAA,OAAO,GAAA,KAAQ,WAAW,CAAA,GAAI,GAAA;AAC/B;AAEA,SAAS,cAAc,OAAA,EAAsC;AAC5D,EAAA,MAAM,CAAA,GAAI,iBAAiB,OAAO,CAAA;AAClC,EAAA,IAAI,CAAA,KAAM,CAAA,EAAG,OAAO,OAAA,CAAQ,KAAA,EAAM;AAClC,EAAA,MAAM,EAAA,GAAK,IAAI,MAAA,CAAO,CAAA,UAAA,EAAa,CAAC,CAAA,CAAA,CAAG,CAAA;AACvC,EAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,EAAG,CAAA,KAAM;AAC5B,IAAA,MAAM,KAAA,GAAQ,CAAA,CAAE,KAAA,CAAM,IAAI,CAAA;AAC1B,IAAA,OAAO,KAAA,CACL,GAAA,CAAI,CAAC,IAAA,EAAM,CAAA,KAAM;AACjB,MAAA,MAAM,WAAA,GAAc,CAAA,GAAI,CAAA,IAAK,CAAA,KAAM,CAAA;AACnC,MAAA,OAAO,WAAA,GAAc,IAAA,CAAK,OAAA,CAAQ,EAAA,EAAI,EAAE,CAAA,GAAI,IAAA;AAAA,IAC7C,CAAC,CAAA,CACA,IAAA,CAAK,IAAI,CAAA;AAAA,EACZ,CAAC,CAAA;AACF;AAEA,SAAS,QAAA,CACR,OAAA,EACA,MAAA,EACA,QAAA,EACS;AACT,EAAA,MAAM,MAAgB,EAAC;AACvB,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,OAAA,CAAQ,QAAQ,CAAA,EAAA,EAAK;AACxC,IAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,CAAC,CAAA,CAAG,MAAM,IAAI,CAAA;AACpC,IAAA,MAAM,UAAA,GAAa,KAAA,CAAM,GAAA,EAAI,IAAK,EAAA;AAClC,IAAA,KAAA,MAAW,CAAA,IAAK,KAAA,EAAO,GAAA,CAAI,IAAA,CAAK,GAAG,IAAI,CAAA;AACvC,IAAA,GAAA,CAAI,KAAK,UAAU,CAAA;AACnB,IAAA,IAAI,CAAA,GAAI,OAAO,MAAA,EAAQ;AACtB,MAAA,MAAM,IAAA,GAAO,QAAQ,MAAA,CAAO,CAAC,GAAI,QAAQ,CAAA,CAAE,MAAM,IAAI,CAAA;AACrD,MAAA,GAAA,CAAI,IAAA,CAAK,IAAA,CAAK,CAAC,CAAE,CAAA;AACjB,MAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,IAAA,CAAK,MAAA,EAAQ,CAAA,EAAA,EAAK,GAAA,CAAI,IAAA,CAAK,IAAA,EAAM,UAAA,EAAY,IAAA,CAAK,CAAC,CAAE,CAAA;AAAA,IAC1E;AAAA,EACD;AACA,EAAA,OAAO,GAAA,CAAI,KAAK,EAAE,CAAA;AACnB;AAEA,SAAS,OAAA,CACR,OAAA,EACA,MAAA,EACA,IAAA,EACS;AACT,EAAA,MAAM,MAAA,GAAS,KAAK,MAAA,KAAW,KAAA;AAC/B,EAAA,MAAM,QAAA,GAAW,aAAa,IAAI,CAAA;AAClC,EAAA,MAAM,SAAA,GAAY,cAAc,IAAI,CAAA;AACpC,EAAA,MAAM,WAAW,MAAA,GAAS,aAAA,CAAc,OAAO,CAAA,GAAI,QAAQ,KAAA,EAAM;AACjE,EAAA,IAAI,MAAA,GAAS,QAAA,CAAS,QAAA,EAAU,MAAA,EAAQ,QAAQ,CAAA;AAChD,EAAA,MAAA,GAAS,MAAA,CAAO,QAAQ,WAAA,EAAa,IAAI,EAAE,OAAA,CAAQ,SAAA,EAAW,MAAM,CAAA,CAAE,IAAA,EAAK;AAC3E,EAAA,IAAI,UAAU,OAAA,EAAS;AACtB,IAAA,MAAM,GAAA,GAAM,QAAQ,SAAS,CAAA;AAC7B,IAAA,MAAA,GAAS,SAAA,CAAU,MAAA,GAChB,SAAA,CAAU,MAAA,CAAO,QAAQ,GAAG,CAAA,GAC5B,eAAA,CAAgB,MAAA,EAAQ,SAAA,CAAU,IAAA,EAAM,SAAA,CAAU,QAAA,EAAU,UAAU,MAAM,CAAA;AAAA,EAChF;AACA,EAAA,OAAO,MAAA;AACR;AAQO,SAAS,MAAA,CAAO,YAAkC,MAAA,EAA+B;AACvF,EAAA,OAAO,OAAA,CAAQ,OAAA,EAAS,MAAA,EAAQ,EAAE,CAAA;AACnC;AAMO,SAAS,YAAA,CAAa,OAAA,GAAyB,EAAC,EAAkB;AACxE,EAAA,QAAQ,CAAC,OAAA,EAAA,GAAkC,MAAA,KAC1C,OAAA,CAAQ,OAAA,EAAS,QAAQ,OAAO,CAAA;AAClC;AAEO,IAAM,EAAA,GAAoB","file":"index.js","sourcesContent":["export class JsonValue<T = unknown> {\n\tconstructor(public readonly value: T) {}\n}\n\nexport const renderPrompt = Symbol('prompt.render')\n\ntype ZodLike = { toJSONSchema(input?: { io: 'input' | 'output' }): object }\ntype Renderable = { [renderPrompt](): PromptValue }\n\ntype Primitive =\n\t| string\n\t| number\n\t| boolean\n\t| null\n\t| undefined\n\t| object\n\t| JsonValue\n\t| ZodLike\n\t| Renderable\n\nexport type PromptValue = Primitive | readonly PromptValue[]\n\nexport type TruncateMode = 'end' | 'start' | 'middle'\nexport type TruncateMarker = string | ((removedChars: number) => string)\n\nexport interface TruncateCtx {\n\tmaxChars: number\n\tmode: TruncateMode\n\tmarker: TruncateMarker\n\ttruncate: (text: string, mode?: TruncateMode) => string\n}\n\nexport type OutputTruncateFn = (text: string, ctx: TruncateCtx) => string\nexport type ValueTruncateFn = (rendered: string, original: PromptValue, ctx: TruncateCtx) => string\n\ntype OutputTruncate =\n\t| { maxChars: number; truncate?: TruncateMode; marker?: TruncateMarker }\n\t| { truncate: OutputTruncateFn; maxChars?: number; marker?: TruncateMarker }\n\ntype ValueTruncate =\n\t| { valueMaxChars: number; valueTruncate?: TruncateMode; valueMarker?: TruncateMarker }\n\t| { valueTruncate: ValueTruncateFn; valueMaxChars?: number; valueMarker?: TruncateMarker }\n\nexport type PromptOptions =\n\t& { dedent?: boolean }\n\t& Partial<OutputTruncate>\n\t& Partial<ValueTruncate>\n\nconst defaultMarker: TruncateMarker = (removed) => `…[truncated ${removed} chars]`\n\nfunction resolveMarker(m: TruncateMarker | undefined, removed: number): string {\n\tconst mk = m ?? defaultMarker\n\treturn typeof mk === 'function' ? mk(removed) : mk\n}\n\nfunction builtinTruncate(\n\ttext: string,\n\tmode: TruncateMode,\n\tmaxChars: number,\n\tmarker: TruncateMarker,\n): string {\n\tif (text.length <= maxChars) return text\n\tconst estRemoved = text.length - maxChars\n\tconst markerStr = resolveMarker(marker, estRemoved)\n\tconst budget = Math.max(0, maxChars - markerStr.length)\n\tif (budget <= 0) return markerStr.slice(0, maxChars)\n\tswitch (mode) {\n\t\tcase 'end':\n\t\t\treturn text.slice(0, budget) + markerStr\n\t\tcase 'start':\n\t\t\treturn markerStr + text.slice(text.length - budget)\n\t\tcase 'middle': {\n\t\t\tconst head = Math.ceil(budget / 2)\n\t\t\tconst tail = budget - head\n\t\t\treturn text.slice(0, head) + markerStr + text.slice(text.length - tail)\n\t\t}\n\t}\n}\n\ninterface ResolvedOutput {\n\tenabled: boolean\n\tmaxChars: number\n\tmode: TruncateMode\n\tmarker: TruncateMarker\n\tcustom?: OutputTruncateFn\n}\n\ninterface ResolvedValue {\n\tenabled: boolean\n\tmaxChars: number\n\tmode: TruncateMode\n\tmarker: TruncateMarker\n\tcustom?: ValueTruncateFn\n}\n\nfunction resolveOutput(opts: PromptOptions): ResolvedOutput {\n\tconst t = opts.truncate\n\tconst custom = typeof t === 'function' ? (t as OutputTruncateFn) : undefined\n\tconst mode: TruncateMode = typeof t === 'string' ? t : 'end'\n\tconst maxChars = opts.maxChars ?? 0\n\treturn {\n\t\tenabled: custom !== undefined || maxChars > 0,\n\t\tmaxChars,\n\t\tmode,\n\t\tmarker: opts.marker ?? defaultMarker,\n\t\tcustom,\n\t}\n}\n\nfunction resolveValue(opts: PromptOptions): ResolvedValue {\n\tconst t = opts.valueTruncate\n\tconst custom = typeof t === 'function' ? (t as ValueTruncateFn) : undefined\n\tconst mode: TruncateMode = typeof t === 'string' ? t : 'end'\n\tconst maxChars = opts.valueMaxChars ?? 0\n\treturn {\n\t\tenabled: custom !== undefined || maxChars > 0,\n\t\tmaxChars,\n\t\tmode,\n\t\tmarker: opts.valueMarker ?? defaultMarker,\n\t\tcustom,\n\t}\n}\n\nfunction makeCtx(r: { maxChars: number; mode: TruncateMode; marker: TruncateMarker }): TruncateCtx {\n\treturn {\n\t\tmaxChars: r.maxChars,\n\t\tmode: r.mode,\n\t\tmarker: r.marker,\n\t\ttruncate: (text, mode) => builtinTruncate(text, mode ?? r.mode, r.maxChars, r.marker),\n\t}\n}\n\nfunction renderRaw(value: PromptValue, valueCfg: ResolvedValue): string {\n\tif (Array.isArray(value)) {\n\t\treturn value\n\t\t\t.filter((v) => v !== null && v !== undefined && v !== false)\n\t\t\t.map((v) => flatten(v as PromptValue, valueCfg))\n\t\t\t.join('\\n')\n\t}\n\tif (value === null || value === undefined || value === false) return ''\n\tif (typeof value === 'object') {\n\t\tconst r = (value as Record<symbol, unknown>)[renderPrompt]\n\t\tif (typeof r === 'function') {\n\t\t\treturn flatten((r as () => PromptValue).call(value), valueCfg)\n\t\t}\n\t\tlet jsonValue: unknown\n\t\tif (value instanceof JsonValue) {\n\t\t\tjsonValue = value.value\n\t\t} else if (typeof (value as ZodLike).toJSONSchema === 'function') {\n\t\t\tjsonValue = (value as ZodLike).toJSONSchema({ io: 'input' })\n\t\t} else {\n\t\t\tjsonValue = value\n\t\t}\n\t\treturn '```json\\n' + JSON.stringify(jsonValue, null, 2) + '\\n```'\n\t}\n\treturn String(value).trim()\n}\n\nfunction flatten(value: PromptValue, valueCfg: ResolvedValue): string {\n\tconst rendered = renderRaw(value, valueCfg)\n\tif (!valueCfg.enabled) return rendered\n\tconst ctx = makeCtx(valueCfg)\n\tif (valueCfg.custom) return valueCfg.custom(rendered, value, ctx)\n\treturn builtinTruncate(rendered, valueCfg.mode, valueCfg.maxChars, valueCfg.marker)\n}\n\nfunction measureMinIndent(strings: readonly string[]): number {\n\t// Placeholder for interpolation slots so lines that contain only an indent\n\t// + a ${value} still count as content lines for indent measurement.\n\tconst joined = strings.join('\\x00')\n\tlet min = Infinity\n\tfor (const line of joined.split('\\n')) {\n\t\tif (!/\\S/.test(line)) continue\n\t\tconst m = /^[ \\t]*/.exec(line)\n\t\tconst ind = m ? m[0].length : 0\n\t\tif (ind < min) min = ind\n\t}\n\treturn min === Infinity ? 0 : min\n}\n\nfunction dedentStrings(strings: readonly string[]): string[] {\n\tconst n = measureMinIndent(strings)\n\tif (n === 0) return strings.slice()\n\tconst re = new RegExp(`^[ \\\\t]{0,${n}}`)\n\treturn strings.map((s, i) => {\n\t\tconst lines = s.split('\\n')\n\t\treturn lines\n\t\t\t.map((line, j) => {\n\t\t\t\tconst isLineStart = j > 0 || i === 0\n\t\t\t\treturn isLineStart ? line.replace(re, '') : line\n\t\t\t})\n\t\t\t.join('\\n')\n\t})\n}\n\nfunction assemble(\n\tstrings: readonly string[],\n\tvalues: readonly PromptValue[],\n\tvalueCfg: ResolvedValue,\n): string {\n\tconst out: string[] = []\n\tfor (let i = 0; i < strings.length; i++) {\n\t\tconst lines = strings[i]!.split('\\n')\n\t\tconst lastPrefix = lines.pop() ?? ''\n\t\tfor (const l of lines) out.push(l, '\\n')\n\t\tout.push(lastPrefix)\n\t\tif (i < values.length) {\n\t\t\tconst flat = flatten(values[i]!, valueCfg).split('\\n')\n\t\t\tout.push(flat[0]!)\n\t\t\tfor (let k = 1; k < flat.length; k++) out.push('\\n', lastPrefix, flat[k]!)\n\t\t}\n\t}\n\treturn out.join('')\n}\n\nfunction compose(\n\tstrings: readonly string[],\n\tvalues: readonly PromptValue[],\n\topts: PromptOptions,\n): string {\n\tconst dedent = opts.dedent !== false\n\tconst valueCfg = resolveValue(opts)\n\tconst outputCfg = resolveOutput(opts)\n\tconst prepared = dedent ? dedentStrings(strings) : strings.slice()\n\tlet result = assemble(prepared, values, valueCfg)\n\tresult = result.replace(/[ \\t]+\\n/g, '\\n').replace(/\\n{3,}/g, '\\n\\n').trim()\n\tif (outputCfg.enabled) {\n\t\tconst ctx = makeCtx(outputCfg)\n\t\tresult = outputCfg.custom\n\t\t\t? outputCfg.custom(result, ctx)\n\t\t\t: builtinTruncate(result, outputCfg.mode, outputCfg.maxChars, outputCfg.marker)\n\t}\n\treturn result\n}\n\n/**\n * Tagged template that assembles a clean LLM prompt string.\n *\n * Preserves the indentation/prefix of the line each `${value}` sits on so\n * lists, block quotes, and multi-line interpolations render correctly.\n */\nexport function prompt(strings: TemplateStringsArray, ...values: PromptValue[]): string {\n\treturn compose(strings, values, {})\n}\n\n/**\n * Returns a configured `prompt` tag. With no arguments it behaves identically\n * to bare {@link prompt}.\n */\nexport function promptCreate(options: PromptOptions = {}): typeof prompt {\n\treturn ((strings: TemplateStringsArray, ...values: PromptValue[]) =>\n\t\tcompose(strings, values, options)) as typeof prompt\n}\n\nexport const ai: typeof prompt = prompt\n"]}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@svara/prompts",
3
+ "version": "0.1.0",
4
+ "description": "Tiny ergonomic tagged-template utility for assembling LLM prompts",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js",
16
+ "require": "./dist/index.cjs"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "README.md"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsup",
25
+ "dev": "tsup --watch",
26
+ "test": "bun test",
27
+ "typecheck": "tsc --noEmit",
28
+ "prepublishOnly": "bun run typecheck && bun run test && bun run build"
29
+ },
30
+ "keywords": [
31
+ "prompt",
32
+ "llm",
33
+ "template",
34
+ "anthropic",
35
+ "openai",
36
+ "tagged-template"
37
+ ],
38
+ "license": "MIT",
39
+ "author": "Borut Svara <borut@svara.io>",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/svaraborut/prompts.git"
43
+ },
44
+ "devDependencies": {
45
+ "@types/bun": "latest",
46
+ "tsup": "^8.3.5",
47
+ "typescript": "^5.7.2"
48
+ }
49
+ }