@anythingai/teleprompt 0.2.0 → 0.3.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.cjs CHANGED
@@ -1,25 +1,7 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;// src/builder.ts
2
- function assertNever(value) {
3
- throw new Error(`Unexpected format: ${value}`);
4
- }
5
- function isSection(node) {
6
- return "render" in node;
7
- }
8
- function isGroup(node) {
9
- return "children" in node;
10
- }
11
- function isOneOf(node) {
12
- return "candidates" in node;
13
- }
14
2
  var PromptBuilder = (_class = class _PromptBuilder {constructor() { _class.prototype.__init.call(this); }
15
3
  __init() {this.nodes = []}
16
- /**
17
- * Add a section to the prompt.
18
- *
19
- * If a section with the same `id` already exists, it is replaced.
20
- * This makes `.use()` idempotent — you can safely call it multiple
21
- * times with the same section without creating duplicates.
22
- */
4
+ /** Replaces an existing section with the same id. */
23
5
  use(section2) {
24
6
  const idx = this.nodes.findIndex((n) => !isOneOf(n) && n.id === section2.id);
25
7
  if (idx >= 0) {
@@ -30,8 +12,7 @@ var PromptBuilder = (_class = class _PromptBuilder {constructor() { _class.proto
30
12
  return this;
31
13
  }
32
14
  /**
33
- * Add mutually exclusive sections. The first candidate that renders
34
- * a non-empty string wins — the rest are excluded.
15
+ * First candidate that renders a non-empty string wins.
35
16
  *
36
17
  * @example
37
18
  * ```ts
@@ -43,9 +24,7 @@ var PromptBuilder = (_class = class _PromptBuilder {constructor() { _class.proto
43
24
  return this;
44
25
  }
45
26
  /**
46
- * Add a named group of sections. In `xml` format, children are
47
- * wrapped in `<id>...</id>`. In `text` format, groups are transparent
48
- * and children render as if they were top-level sections.
27
+ * In `xml` format, wraps children in `<id>` tags. Transparent in `text` format.
49
28
  *
50
29
  * @example
51
30
  * ```ts
@@ -67,199 +46,183 @@ var PromptBuilder = (_class = class _PromptBuilder {constructor() { _class.proto
67
46
  }
68
47
  return this;
69
48
  }
70
- /** Remove a section or group. Accepts an id string or an object with `id`. Searches recursively into groups and oneOf candidates. */
49
+ /** Searches recursively into groups and oneOf candidates. */
71
50
  without(ref) {
72
51
  const id = typeof ref === "string" ? ref : ref.id;
73
- this.nodes = this.removeNode(this.nodes, id);
52
+ this.nodes = removeNode(this.nodes, id);
74
53
  return this;
75
54
  }
76
- /** Check if a section or group exists. Accepts an id string or an object with `id`. Searches recursively into groups and oneOf candidates. */
55
+ /** Searches recursively into groups and oneOf candidates. */
77
56
  has(ref) {
78
57
  const id = typeof ref === "string" ? ref : ref.id;
79
- return this.hasNode(this.nodes, id);
58
+ return hasNode(this.nodes, id);
80
59
  }
81
- /** Get all ids (sections, groups, and oneOf candidates) in order. */
82
60
  ids() {
83
- return this.collectIds(this.nodes);
61
+ return collectIds(this.nodes);
84
62
  }
85
63
  /**
86
- * Create an independent copy of this builder.
87
- *
88
- * Use this to create mode-specific or model-specific variants
89
- * without mutating the base builder.
64
+ * Creates an independent copy. Modifications to the fork don't affect the original.
90
65
  *
91
66
  * @example
92
67
  * ```ts
93
68
  * const base = new PromptBuilder().use(a).use(b);
94
- * const variant = base.fork().use(c); // base is unchanged
69
+ * const variant = base.fork().use(c);
95
70
  * ```
96
71
  */
97
72
  fork() {
98
73
  const forked = new _PromptBuilder();
99
- forked.nodes = this.deepCopy(this.nodes);
74
+ forked.nodes = deepCopy(this.nodes);
100
75
  return forked;
101
76
  }
102
- /**
103
- * Build the final prompt string.
104
- *
105
- * 1. Filters out sections whose `when` guard returns false
106
- * 2. Renders each section
107
- * 3. Filters out empty strings
108
- * 4. Joins with separator and trims
109
- *
110
- * Pass `{ format: 'xml' }` to wrap each section in `<id>` tags.
111
- */
112
77
  build(ctx, options) {
113
78
  return this.buildWithMeta(ctx, options).prompt;
114
79
  }
115
- /**
116
- * Build the prompt and return metadata about which sections were
117
- * included/excluded. Useful for debugging and logging.
118
- *
119
- * A section is "excluded" if its `when` guard returns false.
120
- * A section is "included" only if it passes the guard and renders
121
- * a non-empty string.
122
- */
80
+ /** Like `build`, but also returns which section ids were included/excluded. */
123
81
  buildWithMeta(ctx, options) {
124
82
  const included = [];
125
83
  const excluded = [];
126
84
  const format = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _ => _.format]), () => ( "text"));
127
- const parts = this.renderNodes(this.nodes, ctx, format, included, excluded);
85
+ const parts = renderNodes(this.nodes, ctx, format, included, excluded);
128
86
  const prompt = parts.join("\n\n").trim();
129
87
  return { prompt, included, excluded };
130
88
  }
131
- // --- Private ---
132
- renderNodes(nodes, ctx, format, included, excluded) {
133
- const parts = [];
134
- for (const node of nodes) {
135
- if (isSection(node)) {
136
- this.renderSection(node, ctx, format, parts, included, excluded);
137
- } else if (isGroup(node)) {
138
- const childParts = this.renderNodes(node.children, ctx, format, included, excluded);
139
- if (childParts.length > 0) {
140
- parts.push(...this.formatGroup(node.id, childParts, format));
141
- }
142
- } else {
143
- this.renderOneOf(node, ctx, format, parts, included, excluded);
144
- }
145
- }
146
- return parts;
89
+ }, _class);
90
+ function isSection(node) {
91
+ return "render" in node;
92
+ }
93
+ function isGroup(node) {
94
+ return "children" in node;
95
+ }
96
+ function isOneOf(node) {
97
+ return "candidates" in node;
98
+ }
99
+ function assertNever(value) {
100
+ throw new Error(`Unexpected format: ${value}`);
101
+ }
102
+ function formatSection(id, content, format) {
103
+ switch (format) {
104
+ case "text":
105
+ return content;
106
+ case "xml":
107
+ return `<${id}>
108
+ ${content}
109
+ </${id}>`;
110
+ default:
111
+ return assertNever(format);
147
112
  }
148
- renderSection(section2, ctx, format, parts, included, excluded) {
149
- if (section2.when && !section2.when(ctx)) {
150
- excluded.push(section2.id);
151
- return false;
152
- }
153
- const output = section2.render(ctx);
154
- if (output) {
155
- included.push(section2.id);
156
- parts.push(this.formatSection(section2.id, output, format));
157
- return true;
158
- }
113
+ }
114
+ function formatGroup(id, childParts, format) {
115
+ switch (format) {
116
+ case "text":
117
+ return childParts;
118
+ case "xml":
119
+ return [`<${id}>
120
+ ${childParts.join("\n\n")}
121
+ </${id}>`];
122
+ default:
123
+ return assertNever(format);
124
+ }
125
+ }
126
+ function renderSection(section2, ctx, format, parts, included, excluded) {
127
+ if (section2.when && !section2.when(ctx)) {
159
128
  excluded.push(section2.id);
160
129
  return false;
161
130
  }
162
- renderOneOf(node, ctx, format, parts, included, excluded) {
163
- let found = false;
164
- for (const candidate of node.candidates) {
165
- if (found) {
166
- excluded.push(candidate.id);
167
- continue;
168
- }
169
- if (this.renderSection(candidate, ctx, format, parts, included, excluded)) {
170
- found = true;
171
- }
172
- }
131
+ const output = section2.render(ctx);
132
+ if (output) {
133
+ included.push(section2.id);
134
+ parts.push(formatSection(section2.id, output, format));
135
+ return true;
173
136
  }
174
- /** Wrap a single section's rendered content according to the output format. */
175
- formatSection(id, content, format) {
176
- switch (format) {
177
- case "text":
178
- return content;
179
- case "xml":
180
- return `<${id}>
181
- ${content}
182
- </${id}>`;
183
- default:
184
- return assertNever(format);
137
+ excluded.push(section2.id);
138
+ return false;
139
+ }
140
+ function renderOneOf(node, ctx, format, parts, included, excluded) {
141
+ let found = false;
142
+ for (const candidate of node.candidates) {
143
+ if (found) {
144
+ excluded.push(candidate.id);
145
+ continue;
185
146
  }
186
- }
187
- /**
188
- * Wrap a group's rendered children according to the output format.
189
- * Returns an array — in text mode the children are spread inline,
190
- * in xml mode they are joined and wrapped in a single entry.
191
- */
192
- formatGroup(id, childParts, format) {
193
- switch (format) {
194
- case "text":
195
- return childParts;
196
- case "xml":
197
- return [`<${id}>
198
- ${childParts.join("\n\n")}
199
- </${id}>`];
200
- default:
201
- return assertNever(format);
147
+ if (renderSection(candidate, ctx, format, parts, included, excluded)) {
148
+ found = true;
202
149
  }
203
150
  }
204
- hasNode(nodes, id) {
205
- for (const node of nodes) {
206
- if (isSection(node)) {
207
- if (node.id === id) return true;
208
- } else if (isGroup(node)) {
209
- if (node.id === id) return true;
210
- if (this.hasNode(node.children, id)) return true;
211
- } else {
212
- for (const c of node.candidates) {
213
- if (c.id === id) return true;
214
- }
151
+ }
152
+ function renderNodes(nodes, ctx, format, included, excluded) {
153
+ const parts = [];
154
+ for (const node of nodes) {
155
+ if (isSection(node)) {
156
+ renderSection(node, ctx, format, parts, included, excluded);
157
+ } else if (isGroup(node)) {
158
+ const childParts = renderNodes(node.children, ctx, format, included, excluded);
159
+ if (childParts.length > 0) {
160
+ parts.push(...formatGroup(node.id, childParts, format));
215
161
  }
162
+ } else {
163
+ renderOneOf(node, ctx, format, parts, included, excluded);
216
164
  }
217
- return false;
218
165
  }
219
- removeNode(nodes, id) {
220
- const result = [];
221
- for (const n of nodes) {
222
- if (isSection(n)) {
223
- if (n.id !== id) result.push(n);
224
- } else if (isGroup(n)) {
225
- if (n.id !== id) {
226
- result.push({ ...n, children: this.removeNode(n.children, id) });
227
- }
228
- } else {
229
- const remaining = n.candidates.filter((c) => c.id !== id);
230
- if (remaining.length > 0) {
231
- result.push({ candidates: remaining });
232
- }
166
+ return parts;
167
+ }
168
+ function hasNode(nodes, id) {
169
+ for (const node of nodes) {
170
+ if (isSection(node)) {
171
+ if (node.id === id) return true;
172
+ } else if (isGroup(node)) {
173
+ if (node.id === id) return true;
174
+ if (hasNode(node.children, id)) return true;
175
+ } else {
176
+ for (const c of node.candidates) {
177
+ if (c.id === id) return true;
233
178
  }
234
179
  }
235
- return result;
236
180
  }
237
- collectIds(nodes) {
238
- const ids = [];
239
- for (const node of nodes) {
240
- if (isSection(node)) {
241
- ids.push(node.id);
242
- } else if (isGroup(node)) {
243
- ids.push(node.id);
244
- ids.push(...this.collectIds(node.children));
245
- } else {
246
- ids.push(...node.candidates.map((c) => c.id));
181
+ return false;
182
+ }
183
+ function removeNode(nodes, id) {
184
+ const result = [];
185
+ for (const n of nodes) {
186
+ if (isSection(n)) {
187
+ if (n.id !== id) result.push(n);
188
+ } else if (isGroup(n)) {
189
+ if (n.id !== id) {
190
+ result.push({ ...n, children: removeNode(n.children, id) });
191
+ }
192
+ } else {
193
+ const remaining = n.candidates.filter((c) => c.id !== id);
194
+ if (remaining.length > 0) {
195
+ result.push({ candidates: remaining });
247
196
  }
248
197
  }
249
- return ids;
250
198
  }
251
- deepCopy(nodes) {
252
- return nodes.map((n) => {
253
- if (isGroup(n)) {
254
- return { ...n, children: this.deepCopy(n.children) };
255
- }
256
- if (isOneOf(n)) {
257
- return { candidates: [...n.candidates] };
258
- }
259
- return n;
260
- });
199
+ return result;
200
+ }
201
+ function collectIds(nodes) {
202
+ const ids = [];
203
+ for (const node of nodes) {
204
+ if (isSection(node)) {
205
+ ids.push(node.id);
206
+ } else if (isGroup(node)) {
207
+ ids.push(node.id);
208
+ ids.push(...collectIds(node.children));
209
+ } else {
210
+ ids.push(...node.candidates.map((c) => c.id));
211
+ }
261
212
  }
262
- }, _class);
213
+ return ids;
214
+ }
215
+ function deepCopy(nodes) {
216
+ return nodes.map((n) => {
217
+ if (isGroup(n)) {
218
+ return { ...n, children: deepCopy(n.children) };
219
+ }
220
+ if (isOneOf(n)) {
221
+ return { candidates: [...n.candidates] };
222
+ }
223
+ return n;
224
+ });
225
+ }
263
226
 
264
227
  // src/section.ts
265
228
  function section(id, render) {
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/asurve/dev/teleprompt/dist/index.cjs","../src/builder.ts","../src/section.ts"],"names":["section"],"mappings":"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","file":"/Users/asurve/dev/teleprompt/dist/index.cjs","sourcesContent":[null,"import type { PromptContext, PromptSection } from './types';\n\nfunction assertNever(value: never): never {\n throw new Error(`Unexpected format: ${value}`);\n}\n\n/**\n * A named group of prompt nodes, rendered as an XML wrapper\n * in `xml` format and transparently in `text` format.\n */\ninterface PromptGroup<TCtx extends PromptContext> {\n id: string;\n children: PromptNode<TCtx>[];\n}\n\n/** Mutually exclusive sections — the first candidate that renders wins. */\ninterface PromptOneOf<TCtx extends PromptContext> {\n candidates: PromptSection<TCtx>[];\n}\n\ntype PromptNode<TCtx extends PromptContext> =\n | PromptSection<TCtx>\n | PromptGroup<TCtx>\n | PromptOneOf<TCtx>;\n\nfunction isSection<TCtx extends PromptContext>(\n node: PromptNode<TCtx>,\n): node is PromptSection<TCtx> {\n return 'render' in node;\n}\n\nfunction isGroup<TCtx extends PromptContext>(node: PromptNode<TCtx>): node is PromptGroup<TCtx> {\n return 'children' in node;\n}\n\nfunction isOneOf<TCtx extends PromptContext>(node: PromptNode<TCtx>): node is PromptOneOf<TCtx> {\n return 'candidates' in node;\n}\n\n/** Supported output formats for prompt rendering. */\nexport type PromptFormat = 'text' | 'xml';\n\n/** Options for {@link PromptBuilder.build} and {@link PromptBuilder.buildWithMeta}. */\nexport interface BuildOptions {\n /**\n * Output format.\n *\n * - `'text'` (default) — sections joined with `\\n\\n`, groups are transparent.\n * - `'xml'` — each section wrapped in `<id>...</id>` tags, groups\n * wrapped in `<id>...</id>` containing their children.\n */\n format?: PromptFormat;\n}\n\n/**\n * Declarative, composable prompt builder.\n *\n * Prompts are composed from discrete {@link PromptSection}s that are\n * independently testable pure functions. The builder handles ordering,\n * conditional inclusion, and final assembly.\n *\n * @example\n * ```ts\n * const prompt = new PromptBuilder()\n * .use(identitySection)\n * .use(rulesSection)\n * .use(featureSection)\n * .build(ctx);\n * ```\n */\nexport class PromptBuilder<TCtx extends PromptContext = PromptContext> {\n private nodes: PromptNode<TCtx>[] = [];\n\n /**\n * Add a section to the prompt.\n *\n * If a section with the same `id` already exists, it is replaced.\n * This makes `.use()` idempotent — you can safely call it multiple\n * times with the same section without creating duplicates.\n */\n use(section: PromptSection<TCtx>): this {\n const idx = this.nodes.findIndex((n) => !isOneOf(n) && n.id === section.id);\n if (idx >= 0) {\n this.nodes[idx] = section;\n } else {\n this.nodes.push(section);\n }\n return this;\n }\n\n /**\n * Add mutually exclusive sections. The first candidate that renders\n * a non-empty string wins — the rest are excluded.\n *\n * @example\n * ```ts\n * builder.useOneOf(activeTasks, noActiveTasks)\n * ```\n */\n useOneOf(...candidates: PromptSection<TCtx>[]): this {\n this.nodes.push({ candidates });\n return this;\n }\n\n /**\n * Add a named group of sections. In `xml` format, children are\n * wrapped in `<id>...</id>`. In `text` format, groups are transparent\n * and children render as if they were top-level sections.\n *\n * @example\n * ```ts\n * builder.group('tools', b => b\n * .use(bashSection)\n * .use(gitSection)\n * )\n * ```\n */\n group(id: string, configure: (builder: PromptBuilder<TCtx>) => void): this {\n const inner = new PromptBuilder<TCtx>();\n configure(inner);\n const group: PromptGroup<TCtx> = { id, children: inner.nodes };\n const idx = this.nodes.findIndex((n) => !isOneOf(n) && n.id === id);\n if (idx >= 0) {\n this.nodes[idx] = group;\n } else {\n this.nodes.push(group);\n }\n return this;\n }\n\n /** Remove a section or group. Accepts an id string or an object with `id`. Searches recursively into groups and oneOf candidates. */\n without(ref: string | { id: string }): this {\n const id = typeof ref === 'string' ? ref : ref.id;\n this.nodes = this.removeNode(this.nodes, id);\n return this;\n }\n\n /** Check if a section or group exists. Accepts an id string or an object with `id`. Searches recursively into groups and oneOf candidates. */\n has(ref: string | { id: string }): boolean {\n const id = typeof ref === 'string' ? ref : ref.id;\n return this.hasNode(this.nodes, id);\n }\n\n /** Get all ids (sections, groups, and oneOf candidates) in order. */\n ids(): string[] {\n return this.collectIds(this.nodes);\n }\n\n /**\n * Create an independent copy of this builder.\n *\n * Use this to create mode-specific or model-specific variants\n * without mutating the base builder.\n *\n * @example\n * ```ts\n * const base = new PromptBuilder().use(a).use(b);\n * const variant = base.fork().use(c); // base is unchanged\n * ```\n */\n fork(): PromptBuilder<TCtx> {\n const forked = new PromptBuilder<TCtx>();\n forked.nodes = this.deepCopy(this.nodes);\n return forked;\n }\n\n /**\n * Build the final prompt string.\n *\n * 1. Filters out sections whose `when` guard returns false\n * 2. Renders each section\n * 3. Filters out empty strings\n * 4. Joins with separator and trims\n *\n * Pass `{ format: 'xml' }` to wrap each section in `<id>` tags.\n */\n build(ctx: TCtx, options?: BuildOptions): string {\n return this.buildWithMeta(ctx, options).prompt;\n }\n\n /**\n * Build the prompt and return metadata about which sections were\n * included/excluded. Useful for debugging and logging.\n *\n * A section is \"excluded\" if its `when` guard returns false.\n * A section is \"included\" only if it passes the guard and renders\n * a non-empty string.\n */\n buildWithMeta(\n ctx: TCtx,\n options?: BuildOptions,\n ): {\n prompt: string;\n included: string[];\n excluded: string[];\n } {\n const included: string[] = [];\n const excluded: string[] = [];\n const format = options?.format ?? 'text';\n\n const parts = this.renderNodes(this.nodes, ctx, format, included, excluded);\n const prompt = parts.join('\\n\\n').trim();\n\n return { prompt, included, excluded };\n }\n\n // --- Private ---\n\n private renderNodes(\n nodes: PromptNode<TCtx>[],\n ctx: TCtx,\n format: PromptFormat,\n included: string[],\n excluded: string[],\n ): string[] {\n const parts: string[] = [];\n\n for (const node of nodes) {\n if (isSection(node)) {\n this.renderSection(node, ctx, format, parts, included, excluded);\n } else if (isGroup(node)) {\n const childParts = this.renderNodes(node.children, ctx, format, included, excluded);\n if (childParts.length > 0) {\n parts.push(...this.formatGroup(node.id, childParts, format));\n }\n } else {\n this.renderOneOf(node, ctx, format, parts, included, excluded);\n }\n }\n\n return parts;\n }\n\n private renderSection(\n section: PromptSection<TCtx>,\n ctx: TCtx,\n format: PromptFormat,\n parts: string[],\n included: string[],\n excluded: string[],\n ): boolean {\n if (section.when && !section.when(ctx)) {\n excluded.push(section.id);\n return false;\n }\n const output = section.render(ctx);\n if (output) {\n included.push(section.id);\n parts.push(this.formatSection(section.id, output, format));\n return true;\n }\n excluded.push(section.id);\n return false;\n }\n\n private renderOneOf(\n node: PromptOneOf<TCtx>,\n ctx: TCtx,\n format: PromptFormat,\n parts: string[],\n included: string[],\n excluded: string[],\n ): void {\n let found = false;\n for (const candidate of node.candidates) {\n if (found) {\n excluded.push(candidate.id);\n continue;\n }\n if (this.renderSection(candidate, ctx, format, parts, included, excluded)) {\n found = true;\n }\n }\n }\n\n /** Wrap a single section's rendered content according to the output format. */\n private formatSection(id: string, content: string, format: PromptFormat): string {\n switch (format) {\n case 'text':\n return content;\n case 'xml':\n return `<${id}>\\n${content}\\n</${id}>`;\n default:\n return assertNever(format);\n }\n }\n\n /**\n * Wrap a group's rendered children according to the output format.\n * Returns an array — in text mode the children are spread inline,\n * in xml mode they are joined and wrapped in a single entry.\n */\n private formatGroup(id: string, childParts: string[], format: PromptFormat): string[] {\n switch (format) {\n case 'text':\n return childParts;\n case 'xml':\n return [`<${id}>\\n${childParts.join('\\n\\n')}\\n</${id}>`];\n default:\n return assertNever(format);\n }\n }\n\n private hasNode(nodes: PromptNode<TCtx>[], id: string): boolean {\n for (const node of nodes) {\n if (isSection(node)) {\n if (node.id === id) return true;\n } else if (isGroup(node)) {\n if (node.id === id) return true;\n if (this.hasNode(node.children, id)) return true;\n } else {\n for (const c of node.candidates) {\n if (c.id === id) return true;\n }\n }\n }\n return false;\n }\n\n private removeNode(nodes: PromptNode<TCtx>[], id: string): PromptNode<TCtx>[] {\n const result: PromptNode<TCtx>[] = [];\n for (const n of nodes) {\n if (isSection(n)) {\n if (n.id !== id) result.push(n);\n } else if (isGroup(n)) {\n if (n.id !== id) {\n result.push({ ...n, children: this.removeNode(n.children, id) });\n }\n } else {\n const remaining = n.candidates.filter((c) => c.id !== id);\n if (remaining.length > 0) {\n result.push({ candidates: remaining });\n }\n }\n }\n return result;\n }\n\n private collectIds(nodes: PromptNode<TCtx>[]): string[] {\n const ids: string[] = [];\n for (const node of nodes) {\n if (isSection(node)) {\n ids.push(node.id);\n } else if (isGroup(node)) {\n ids.push(node.id);\n ids.push(...this.collectIds(node.children));\n } else {\n ids.push(...node.candidates.map((c) => c.id));\n }\n }\n return ids;\n }\n\n private deepCopy(nodes: PromptNode<TCtx>[]): PromptNode<TCtx>[] {\n return nodes.map((n) => {\n if (isGroup(n)) {\n return { ...n, children: this.deepCopy(n.children) };\n }\n if (isOneOf(n)) {\n return { candidates: [...n.candidates] };\n }\n return n;\n });\n }\n}\n","import type { PromptContext, PromptSection } from './types';\n\n/**\n * Create a prompt section. Return a string to include, `null` to exclude.\n *\n * @example\n * ```ts\n * // Static\n * section('identity', () => 'You are Coworker.')\n *\n * // Conditional — return null to exclude\n * section('prod', (ctx: SandboxCtx) => {\n * if (ctx.vars.prodContext == null) return null;\n * return `## Prod\\n\\n${ctx.vars.prodContext}`;\n * })\n * ```\n */\nexport function section<TCtx extends PromptContext = PromptContext>(\n id: string,\n render: (ctx: TCtx) => string | null,\n): PromptSection<TCtx> {\n return {\n id,\n render: (ctx) => render(ctx) ?? '',\n };\n}\n"]}
1
+ {"version":3,"sources":["/Users/asurve/dev/teleprompt/main/dist/index.cjs","../src/builder.ts","../src/section.ts"],"names":["section"],"mappings":"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","file":"/Users/asurve/dev/teleprompt/main/dist/index.cjs","sourcesContent":[null,"import type { PromptContext, PromptSection } from './types';\n\nexport type PromptFormat = 'text' | 'xml';\n\nexport interface BuildOptions {\n /**\n * - `'text'` (default) — sections joined with `\\n\\n`, groups are transparent.\n * - `'xml'` — each section wrapped in `<id>...</id>` tags, groups\n * wrapped in `<id>...</id>` containing their children.\n */\n format?: PromptFormat;\n}\n\n/**\n * @example\n * ```ts\n * const prompt = new PromptBuilder()\n * .use(identitySection)\n * .use(rulesSection)\n * .use(featureSection)\n * .build(ctx);\n * ```\n */\nexport class PromptBuilder<TCtx extends PromptContext = PromptContext> {\n private nodes: PromptNode<TCtx>[] = [];\n\n /** Replaces an existing section with the same id. */\n use(section: PromptSection<TCtx>): this {\n const idx = this.nodes.findIndex((n) => !isOneOf(n) && n.id === section.id);\n if (idx >= 0) {\n this.nodes[idx] = section;\n } else {\n this.nodes.push(section);\n }\n return this;\n }\n\n /**\n * First candidate that renders a non-empty string wins.\n *\n * @example\n * ```ts\n * builder.useOneOf(activeTasks, noActiveTasks)\n * ```\n */\n useOneOf(...candidates: PromptSection<TCtx>[]): this {\n this.nodes.push({ candidates });\n return this;\n }\n\n /**\n * In `xml` format, wraps children in `<id>` tags. Transparent in `text` format.\n *\n * @example\n * ```ts\n * builder.group('tools', b => b\n * .use(bashSection)\n * .use(gitSection)\n * )\n * ```\n */\n group(id: string, configure: (builder: PromptBuilder<TCtx>) => void): this {\n const inner = new PromptBuilder<TCtx>();\n configure(inner);\n const group: PromptGroup<TCtx> = { id, children: inner.nodes };\n const idx = this.nodes.findIndex((n) => !isOneOf(n) && n.id === id);\n if (idx >= 0) {\n this.nodes[idx] = group;\n } else {\n this.nodes.push(group);\n }\n return this;\n }\n\n /** Searches recursively into groups and oneOf candidates. */\n without(ref: string | { id: string }): this {\n const id = typeof ref === 'string' ? ref : ref.id;\n this.nodes = removeNode(this.nodes, id);\n return this;\n }\n\n /** Searches recursively into groups and oneOf candidates. */\n has(ref: string | { id: string }): boolean {\n const id = typeof ref === 'string' ? ref : ref.id;\n return hasNode(this.nodes, id);\n }\n\n ids(): string[] {\n return collectIds(this.nodes);\n }\n\n /**\n * Creates an independent copy. Modifications to the fork don't affect the original.\n *\n * @example\n * ```ts\n * const base = new PromptBuilder().use(a).use(b);\n * const variant = base.fork().use(c);\n * ```\n */\n fork(): PromptBuilder<TCtx> {\n const forked = new PromptBuilder<TCtx>();\n forked.nodes = deepCopy(this.nodes);\n return forked;\n }\n\n build(ctx: TCtx, options?: BuildOptions): string {\n return this.buildWithMeta(ctx, options).prompt;\n }\n\n /** Like `build`, but also returns which section ids were included/excluded. */\n buildWithMeta(\n ctx: TCtx,\n options?: BuildOptions,\n ): {\n prompt: string;\n included: string[];\n excluded: string[];\n } {\n const included: string[] = [];\n const excluded: string[] = [];\n const format = options?.format ?? 'text';\n\n const parts = renderNodes(this.nodes, ctx, format, included, excluded);\n const prompt = parts.join('\\n\\n').trim();\n\n return { prompt, included, excluded };\n }\n}\n\ninterface PromptGroup<TCtx extends PromptContext> {\n id: string;\n children: PromptNode<TCtx>[];\n}\n\ninterface PromptOneOf<TCtx extends PromptContext> {\n candidates: PromptSection<TCtx>[];\n}\n\ntype PromptNode<TCtx extends PromptContext> =\n | PromptSection<TCtx>\n | PromptGroup<TCtx>\n | PromptOneOf<TCtx>;\n\nfunction isSection<TCtx extends PromptContext>(\n node: PromptNode<TCtx>,\n): node is PromptSection<TCtx> {\n return 'render' in node;\n}\n\nfunction isGroup<TCtx extends PromptContext>(node: PromptNode<TCtx>): node is PromptGroup<TCtx> {\n return 'children' in node;\n}\n\nfunction isOneOf<TCtx extends PromptContext>(node: PromptNode<TCtx>): node is PromptOneOf<TCtx> {\n return 'candidates' in node;\n}\n\nfunction assertNever(value: never): never {\n throw new Error(`Unexpected format: ${value}`);\n}\n\nfunction formatSection(id: string, content: string, format: PromptFormat): string {\n switch (format) {\n case 'text':\n return content;\n case 'xml':\n return `<${id}>\\n${content}\\n</${id}>`;\n default:\n return assertNever(format);\n }\n}\n\nfunction formatGroup(id: string, childParts: string[], format: PromptFormat): string[] {\n switch (format) {\n case 'text':\n return childParts;\n case 'xml':\n return [`<${id}>\\n${childParts.join('\\n\\n')}\\n</${id}>`];\n default:\n return assertNever(format);\n }\n}\n\nfunction renderSection<TCtx extends PromptContext>(\n section: PromptSection<TCtx>,\n ctx: TCtx,\n format: PromptFormat,\n parts: string[],\n included: string[],\n excluded: string[],\n): boolean {\n if (section.when && !section.when(ctx)) {\n excluded.push(section.id);\n return false;\n }\n const output = section.render(ctx);\n if (output) {\n included.push(section.id);\n parts.push(formatSection(section.id, output, format));\n return true;\n }\n excluded.push(section.id);\n return false;\n}\n\nfunction renderOneOf<TCtx extends PromptContext>(\n node: PromptOneOf<TCtx>,\n ctx: TCtx,\n format: PromptFormat,\n parts: string[],\n included: string[],\n excluded: string[],\n): void {\n let found = false;\n for (const candidate of node.candidates) {\n if (found) {\n excluded.push(candidate.id);\n continue;\n }\n if (renderSection(candidate, ctx, format, parts, included, excluded)) {\n found = true;\n }\n }\n}\n\nfunction renderNodes<TCtx extends PromptContext>(\n nodes: PromptNode<TCtx>[],\n ctx: TCtx,\n format: PromptFormat,\n included: string[],\n excluded: string[],\n): string[] {\n const parts: string[] = [];\n\n for (const node of nodes) {\n if (isSection(node)) {\n renderSection(node, ctx, format, parts, included, excluded);\n } else if (isGroup(node)) {\n const childParts = renderNodes(node.children, ctx, format, included, excluded);\n if (childParts.length > 0) {\n parts.push(...formatGroup(node.id, childParts, format));\n }\n } else {\n renderOneOf(node, ctx, format, parts, included, excluded);\n }\n }\n\n return parts;\n}\n\nfunction hasNode<TCtx extends PromptContext>(nodes: PromptNode<TCtx>[], id: string): boolean {\n for (const node of nodes) {\n if (isSection(node)) {\n if (node.id === id) return true;\n } else if (isGroup(node)) {\n if (node.id === id) return true;\n if (hasNode(node.children, id)) return true;\n } else {\n for (const c of node.candidates) {\n if (c.id === id) return true;\n }\n }\n }\n return false;\n}\n\nfunction removeNode<TCtx extends PromptContext>(\n nodes: PromptNode<TCtx>[],\n id: string,\n): PromptNode<TCtx>[] {\n const result: PromptNode<TCtx>[] = [];\n for (const n of nodes) {\n if (isSection(n)) {\n if (n.id !== id) result.push(n);\n } else if (isGroup(n)) {\n if (n.id !== id) {\n result.push({ ...n, children: removeNode(n.children, id) });\n }\n } else {\n const remaining = n.candidates.filter((c) => c.id !== id);\n if (remaining.length > 0) {\n result.push({ candidates: remaining });\n }\n }\n }\n return result;\n}\n\nfunction collectIds<TCtx extends PromptContext>(nodes: PromptNode<TCtx>[]): string[] {\n const ids: string[] = [];\n for (const node of nodes) {\n if (isSection(node)) {\n ids.push(node.id);\n } else if (isGroup(node)) {\n ids.push(node.id);\n ids.push(...collectIds(node.children));\n } else {\n ids.push(...node.candidates.map((c) => c.id));\n }\n }\n return ids;\n}\n\nfunction deepCopy<TCtx extends PromptContext>(nodes: PromptNode<TCtx>[]): PromptNode<TCtx>[] {\n return nodes.map((n) => {\n if (isGroup(n)) {\n return { ...n, children: deepCopy(n.children) };\n }\n if (isOneOf(n)) {\n return { candidates: [...n.candidates] };\n }\n return n;\n });\n}\n","import type { PromptContext, PromptSection } from './types';\n\n/**\n * Create a prompt section. Return a string to include, `null` to exclude.\n *\n * @example\n * ```ts\n * section('identity', () => 'You are Coworker.')\n *\n * section('prod', (ctx: SandboxCtx) => {\n * if (ctx.vars.prodContext == null) return null;\n * return `## Prod\\n\\n${ctx.vars.prodContext}`;\n * })\n * ```\n */\nexport function section<TCtx extends PromptContext = PromptContext>(\n id: string,\n render: (ctx: TCtx) => string | null,\n): PromptSection<TCtx> {\n return {\n id,\n render: (ctx) => render(ctx) ?? '',\n };\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -1,12 +1,8 @@
1
- import { P as PromptContext, a as PromptSection } from './types--sRnfw6Q.cjs';
1
+ import { P as PromptContext, a as PromptSection } from './types-ow-XbrO7.cjs';
2
2
 
3
- /** Supported output formats for prompt rendering. */
4
3
  type PromptFormat = 'text' | 'xml';
5
- /** Options for {@link PromptBuilder.build} and {@link PromptBuilder.buildWithMeta}. */
6
4
  interface BuildOptions {
7
5
  /**
8
- * Output format.
9
- *
10
6
  * - `'text'` (default) — sections joined with `\n\n`, groups are transparent.
11
7
  * - `'xml'` — each section wrapped in `<id>...</id>` tags, groups
12
8
  * wrapped in `<id>...</id>` containing their children.
@@ -14,12 +10,6 @@ interface BuildOptions {
14
10
  format?: PromptFormat;
15
11
  }
16
12
  /**
17
- * Declarative, composable prompt builder.
18
- *
19
- * Prompts are composed from discrete {@link PromptSection}s that are
20
- * independently testable pure functions. The builder handles ordering,
21
- * conditional inclusion, and final assembly.
22
- *
23
13
  * @example
24
14
  * ```ts
25
15
  * const prompt = new PromptBuilder()
@@ -31,17 +21,10 @@ interface BuildOptions {
31
21
  */
32
22
  declare class PromptBuilder<TCtx extends PromptContext = PromptContext> {
33
23
  private nodes;
34
- /**
35
- * Add a section to the prompt.
36
- *
37
- * If a section with the same `id` already exists, it is replaced.
38
- * This makes `.use()` idempotent — you can safely call it multiple
39
- * times with the same section without creating duplicates.
40
- */
24
+ /** Replaces an existing section with the same id. */
41
25
  use(section: PromptSection<TCtx>): this;
42
26
  /**
43
- * Add mutually exclusive sections. The first candidate that renders
44
- * a non-empty string wins — the rest are excluded.
27
+ * First candidate that renders a non-empty string wins.
45
28
  *
46
29
  * @example
47
30
  * ```ts
@@ -50,9 +33,7 @@ declare class PromptBuilder<TCtx extends PromptContext = PromptContext> {
50
33
  */
51
34
  useOneOf(...candidates: PromptSection<TCtx>[]): this;
52
35
  /**
53
- * Add a named group of sections. In `xml` format, children are
54
- * wrapped in `<id>...</id>`. In `text` format, groups are transparent
55
- * and children render as if they were top-level sections.
36
+ * In `xml` format, wraps children in `<id>` tags. Transparent in `text` format.
56
37
  *
57
38
  * @example
58
39
  * ```ts
@@ -63,68 +44,32 @@ declare class PromptBuilder<TCtx extends PromptContext = PromptContext> {
63
44
  * ```
64
45
  */
65
46
  group(id: string, configure: (builder: PromptBuilder<TCtx>) => void): this;
66
- /** Remove a section or group. Accepts an id string or an object with `id`. Searches recursively into groups and oneOf candidates. */
47
+ /** Searches recursively into groups and oneOf candidates. */
67
48
  without(ref: string | {
68
49
  id: string;
69
50
  }): this;
70
- /** Check if a section or group exists. Accepts an id string or an object with `id`. Searches recursively into groups and oneOf candidates. */
51
+ /** Searches recursively into groups and oneOf candidates. */
71
52
  has(ref: string | {
72
53
  id: string;
73
54
  }): boolean;
74
- /** Get all ids (sections, groups, and oneOf candidates) in order. */
75
55
  ids(): string[];
76
56
  /**
77
- * Create an independent copy of this builder.
78
- *
79
- * Use this to create mode-specific or model-specific variants
80
- * without mutating the base builder.
57
+ * Creates an independent copy. Modifications to the fork don't affect the original.
81
58
  *
82
59
  * @example
83
60
  * ```ts
84
61
  * const base = new PromptBuilder().use(a).use(b);
85
- * const variant = base.fork().use(c); // base is unchanged
62
+ * const variant = base.fork().use(c);
86
63
  * ```
87
64
  */
88
65
  fork(): PromptBuilder<TCtx>;
89
- /**
90
- * Build the final prompt string.
91
- *
92
- * 1. Filters out sections whose `when` guard returns false
93
- * 2. Renders each section
94
- * 3. Filters out empty strings
95
- * 4. Joins with separator and trims
96
- *
97
- * Pass `{ format: 'xml' }` to wrap each section in `<id>` tags.
98
- */
99
66
  build(ctx: TCtx, options?: BuildOptions): string;
100
- /**
101
- * Build the prompt and return metadata about which sections were
102
- * included/excluded. Useful for debugging and logging.
103
- *
104
- * A section is "excluded" if its `when` guard returns false.
105
- * A section is "included" only if it passes the guard and renders
106
- * a non-empty string.
107
- */
67
+ /** Like `build`, but also returns which section ids were included/excluded. */
108
68
  buildWithMeta(ctx: TCtx, options?: BuildOptions): {
109
69
  prompt: string;
110
70
  included: string[];
111
71
  excluded: string[];
112
72
  };
113
- private renderNodes;
114
- private renderSection;
115
- private renderOneOf;
116
- /** Wrap a single section's rendered content according to the output format. */
117
- private formatSection;
118
- /**
119
- * Wrap a group's rendered children according to the output format.
120
- * Returns an array — in text mode the children are spread inline,
121
- * in xml mode they are joined and wrapped in a single entry.
122
- */
123
- private formatGroup;
124
- private hasNode;
125
- private removeNode;
126
- private collectIds;
127
- private deepCopy;
128
73
  }
129
74
 
130
75
  /**
@@ -132,10 +77,8 @@ declare class PromptBuilder<TCtx extends PromptContext = PromptContext> {
132
77
  *
133
78
  * @example
134
79
  * ```ts
135
- * // Static
136
80
  * section('identity', () => 'You are Coworker.')
137
81
  *
138
- * // Conditional — return null to exclude
139
82
  * section('prod', (ctx: SandboxCtx) => {
140
83
  * if (ctx.vars.prodContext == null) return null;
141
84
  * return `## Prod\n\n${ctx.vars.prodContext}`;