@anythingai/teleprompt 0.1.1 → 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/README.md +137 -57
- package/dist/index.cjs +201 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +55 -41
- package/dist/index.d.ts +55 -41
- package/dist/index.js +200 -57
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +4 -23
- package/dist/testing.d.ts +4 -23
- package/dist/testing.js.map +1 -1
- package/dist/types-ow-XbrO7.d.cts +12 -0
- package/dist/types-ow-XbrO7.d.ts +12 -0
- package/llms.txt +183 -0
- package/package.json +3 -2
- package/dist/types-DXgCksVT.d.cts +0 -34
- package/dist/types-DXgCksVT.d.ts +0 -34
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# teleprompt
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Composable, section-based LLM system prompts.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Each section owns its content and conditional logic. Sections can be reused across prompt variants, conditionally included, and rendered as plain text or XML.
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
pnpm add @anythingai/teleprompt
|
|
@@ -11,35 +11,30 @@ pnpm add @anythingai/teleprompt
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
|
-
import { PromptBuilder,
|
|
14
|
+
import { PromptBuilder, section, type PromptContext } from '@anythingai/teleprompt';
|
|
15
15
|
|
|
16
16
|
// Define your context shape
|
|
17
17
|
type MyFlags = { webSearchEnabled: boolean };
|
|
18
18
|
type MyVars = { assistantName: string };
|
|
19
19
|
type MyContext = PromptContext<MyFlags, MyVars>;
|
|
20
20
|
|
|
21
|
-
//
|
|
22
|
-
const
|
|
23
|
-
id: 'identity',
|
|
24
|
-
render: (ctx) => `You are ${ctx.vars.assistantName}, a helpful AI assistant.`,
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
// This is a static section with no context dependencies
|
|
28
|
-
const guidelines: PromptSection<MyContext> = {
|
|
29
|
-
id: 'guidelines',
|
|
30
|
-
render: () => `# Guidelines
|
|
21
|
+
// Static section — no context needed, no type parameter
|
|
22
|
+
const guidelines = section('guidelines', () => `# Guidelines
|
|
31
23
|
- Be concise and direct.
|
|
32
24
|
- Cite sources when making factual claims.
|
|
33
|
-
- Ask for clarification when a request is ambiguous
|
|
34
|
-
};
|
|
25
|
+
- Ask for clarification when a request is ambiguous.`);
|
|
35
26
|
|
|
36
|
-
//
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
27
|
+
// Dynamic section — uses context
|
|
28
|
+
const identity = section('identity', (ctx: MyContext) =>
|
|
29
|
+
`You are ${ctx.vars.assistantName}, a helpful AI assistant.`
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
// Conditional section — return null to exclude
|
|
33
|
+
const webSearch = section('web-search', (ctx: MyContext) => {
|
|
34
|
+
if (!ctx.flags.webSearchEnabled) return null;
|
|
35
|
+
return `You have access to web search. Use it when the user asks about
|
|
36
|
+
current events or information that may have changed after your training cutoff.`;
|
|
37
|
+
});
|
|
43
38
|
|
|
44
39
|
// Compose and build
|
|
45
40
|
const prompt = new PromptBuilder<MyContext>()
|
|
@@ -54,21 +49,60 @@ const prompt = new PromptBuilder<MyContext>()
|
|
|
54
49
|
|
|
55
50
|
## Sections
|
|
56
51
|
|
|
57
|
-
|
|
52
|
+
`section(id, render)` takes an id and a render function. Return a string to include, `null` to exclude:
|
|
58
53
|
|
|
59
54
|
```ts
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
55
|
+
// Always included
|
|
56
|
+
const rules = section('rules', () => 'Be helpful and concise.');
|
|
57
|
+
|
|
58
|
+
// Conditional — null means excluded
|
|
59
|
+
const citation = section('citation', (ctx: MyContext) => {
|
|
60
|
+
if (!ctx.flags.citationEnabled) return null;
|
|
61
|
+
return 'Always include citations with links when referencing external sources.';
|
|
62
|
+
});
|
|
65
63
|
```
|
|
66
64
|
|
|
67
65
|
Sections render in the order you call `.use()`. To reorder, change the call order.
|
|
68
66
|
|
|
67
|
+
Static sections (no type parameter) work in any builder:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
const disclaimer = section('disclaimer', () => 'Responses are not legal advice.');
|
|
71
|
+
|
|
72
|
+
// Works in any builder regardless of context type
|
|
73
|
+
new PromptBuilder<MyContext>().use(disclaimer)
|
|
74
|
+
new PromptBuilder<OtherContext>().use(disclaimer)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Context
|
|
78
|
+
|
|
79
|
+
Sections receive a typed context with boolean flags and arbitrary variables:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
type MyFlags = {
|
|
83
|
+
webSearchEnabled: boolean;
|
|
84
|
+
citationEnabled: boolean;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
type MyVars = {
|
|
88
|
+
assistantName: string;
|
|
89
|
+
language: string;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
type MyContext = PromptContext<MyFlags, MyVars>;
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`PromptContext` and `PromptBuilder` have defaults, so the type parameter is optional:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
const builder = new PromptBuilder();
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Build the context once and pass it to `.build(ctx)`. Every section receives the same object.
|
|
102
|
+
|
|
69
103
|
## Forking
|
|
70
104
|
|
|
71
|
-
Create variants
|
|
105
|
+
Create variants from a shared base:
|
|
72
106
|
|
|
73
107
|
```ts
|
|
74
108
|
const base = new PromptBuilder<MyContext>()
|
|
@@ -91,50 +125,96 @@ const codeAssistant = base.fork()
|
|
|
91
125
|
|
|
92
126
|
Each fork is independent. Modifying one doesn't affect the others.
|
|
93
127
|
|
|
94
|
-
##
|
|
128
|
+
## XML Format
|
|
95
129
|
|
|
96
|
-
|
|
130
|
+
Both Claude and Gemini recommend structuring prompts with XML tags. Pass `{ format: 'xml' }` to `.build()` to wrap each section in `<id>` tags:
|
|
97
131
|
|
|
98
132
|
```ts
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
citationEnabled: boolean;
|
|
102
|
-
};
|
|
133
|
+
builder.build(ctx, { format: 'xml' })
|
|
134
|
+
```
|
|
103
135
|
|
|
104
|
-
|
|
105
|
-
assistantName: string;
|
|
106
|
-
language: string;
|
|
107
|
-
};
|
|
136
|
+
The section id becomes the tag name. Content is left as-is inside the tags.
|
|
108
137
|
|
|
109
|
-
|
|
138
|
+
## Groups
|
|
139
|
+
|
|
140
|
+
Group related sections together. In text mode, groups are transparent. In XML mode, they wrap children in tags:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
const prompt = new PromptBuilder<MyContext>()
|
|
144
|
+
.use(identity)
|
|
145
|
+
.group('tools', b => b
|
|
146
|
+
.use(webSearch)
|
|
147
|
+
.use(calculator)
|
|
148
|
+
)
|
|
149
|
+
.use(guidelines)
|
|
150
|
+
.build(ctx, { format: 'xml' });
|
|
110
151
|
```
|
|
111
152
|
|
|
112
|
-
|
|
153
|
+
```xml
|
|
154
|
+
<identity>
|
|
155
|
+
You are Daniel, a helpful AI assistant.
|
|
156
|
+
</identity>
|
|
157
|
+
|
|
158
|
+
<tools>
|
|
159
|
+
<web-search>
|
|
160
|
+
You have access to web search...
|
|
161
|
+
</web-search>
|
|
162
|
+
|
|
163
|
+
<calculator>
|
|
164
|
+
You can evaluate math expressions...
|
|
165
|
+
</calculator>
|
|
166
|
+
</tools>
|
|
167
|
+
|
|
168
|
+
<guidelines>
|
|
169
|
+
# Guidelines
|
|
170
|
+
...
|
|
171
|
+
</guidelines>
|
|
172
|
+
```
|
|
113
173
|
|
|
114
|
-
|
|
174
|
+
Groups can be nested:
|
|
115
175
|
|
|
116
176
|
```ts
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
177
|
+
builder.group('capabilities', b => b
|
|
178
|
+
.group('tools', b => b
|
|
179
|
+
.use(webSearch)
|
|
180
|
+
.use(calculator)
|
|
181
|
+
)
|
|
182
|
+
.group('integrations', b => b
|
|
183
|
+
.use(slack)
|
|
184
|
+
.use(linear)
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
```
|
|
120
188
|
|
|
121
|
-
|
|
122
|
-
.without(section)
|
|
189
|
+
## Mutually Exclusive Sections
|
|
123
190
|
|
|
124
|
-
|
|
125
|
-
.has(section)
|
|
191
|
+
Use `.useOneOf()` when exactly one of several sections should render. The first candidate that returns a non-empty string wins:
|
|
126
192
|
|
|
127
|
-
|
|
128
|
-
|
|
193
|
+
```ts
|
|
194
|
+
const hasTasks = section('has-tasks', (ctx: MyContext) => {
|
|
195
|
+
if (ctx.vars.tasks.length === 0) return null;
|
|
196
|
+
return `## Active Tasks\n\n${ctx.vars.tasks.map(t => `- ${t.title}`).join('\n')}`;
|
|
197
|
+
});
|
|
129
198
|
|
|
130
|
-
|
|
131
|
-
.fork()
|
|
199
|
+
const noTasks = section('no-tasks', () => '## Active Tasks\n\nNo tasks currently running.');
|
|
132
200
|
|
|
133
|
-
|
|
134
|
-
|
|
201
|
+
builder.useOneOf(hasTasks, noTasks);
|
|
202
|
+
```
|
|
135
203
|
|
|
136
|
-
|
|
137
|
-
|
|
204
|
+
## Builder API
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
new PromptBuilder<MyContext>()
|
|
208
|
+
.use(section) // append (replaces if same id)
|
|
209
|
+
.useOneOf(sectionA, sectionB) // first match wins
|
|
210
|
+
.group('name', b => b.use(...)) // named group (XML wrapper)
|
|
211
|
+
.without(section) // remove by object or string id
|
|
212
|
+
.has(section) // check existence
|
|
213
|
+
.ids() // list all section ids
|
|
214
|
+
.fork() // independent copy
|
|
215
|
+
.build(ctx) // render to string
|
|
216
|
+
.build(ctx, { format: 'xml' }) // render with XML tags
|
|
217
|
+
.buildWithMeta(ctx) // render + { included, excluded }
|
|
138
218
|
```
|
|
139
219
|
|
|
140
220
|
## Testing
|
package/dist/index.cjs
CHANGED
|
@@ -1,95 +1,238 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); var _class;// src/builder.ts
|
|
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
2
|
var PromptBuilder = (_class = class _PromptBuilder {constructor() { _class.prototype.__init.call(this); }
|
|
3
|
-
__init() {this.
|
|
3
|
+
__init() {this.nodes = []}
|
|
4
|
+
/** Replaces an existing section with the same id. */
|
|
5
|
+
use(section2) {
|
|
6
|
+
const idx = this.nodes.findIndex((n) => !isOneOf(n) && n.id === section2.id);
|
|
7
|
+
if (idx >= 0) {
|
|
8
|
+
this.nodes[idx] = section2;
|
|
9
|
+
} else {
|
|
10
|
+
this.nodes.push(section2);
|
|
11
|
+
}
|
|
12
|
+
return this;
|
|
13
|
+
}
|
|
4
14
|
/**
|
|
5
|
-
*
|
|
15
|
+
* First candidate that renders a non-empty string wins.
|
|
6
16
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* builder.useOneOf(activeTasks, noActiveTasks)
|
|
20
|
+
* ```
|
|
10
21
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
22
|
+
useOneOf(...candidates) {
|
|
23
|
+
this.nodes.push({ candidates });
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* In `xml` format, wraps children in `<id>` tags. Transparent in `text` format.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* builder.group('tools', b => b
|
|
32
|
+
* .use(bashSection)
|
|
33
|
+
* .use(gitSection)
|
|
34
|
+
* )
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
group(id, configure) {
|
|
38
|
+
const inner = new _PromptBuilder();
|
|
39
|
+
configure(inner);
|
|
40
|
+
const group = { id, children: inner.nodes };
|
|
41
|
+
const idx = this.nodes.findIndex((n) => !isOneOf(n) && n.id === id);
|
|
42
|
+
if (idx >= 0) {
|
|
43
|
+
this.nodes[idx] = group;
|
|
15
44
|
} else {
|
|
16
|
-
this.
|
|
45
|
+
this.nodes.push(group);
|
|
17
46
|
}
|
|
18
47
|
return this;
|
|
19
48
|
}
|
|
20
|
-
/**
|
|
49
|
+
/** Searches recursively into groups and oneOf candidates. */
|
|
21
50
|
without(ref) {
|
|
22
51
|
const id = typeof ref === "string" ? ref : ref.id;
|
|
23
|
-
this.
|
|
52
|
+
this.nodes = removeNode(this.nodes, id);
|
|
24
53
|
return this;
|
|
25
54
|
}
|
|
26
|
-
/**
|
|
55
|
+
/** Searches recursively into groups and oneOf candidates. */
|
|
27
56
|
has(ref) {
|
|
28
57
|
const id = typeof ref === "string" ? ref : ref.id;
|
|
29
|
-
return this.
|
|
58
|
+
return hasNode(this.nodes, id);
|
|
30
59
|
}
|
|
31
|
-
/** Get the ids of all registered sections (in insertion order). */
|
|
32
60
|
ids() {
|
|
33
|
-
return this.
|
|
61
|
+
return collectIds(this.nodes);
|
|
34
62
|
}
|
|
35
63
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* Use this to create mode-specific or model-specific variants
|
|
39
|
-
* without mutating the base builder.
|
|
64
|
+
* Creates an independent copy. Modifications to the fork don't affect the original.
|
|
40
65
|
*
|
|
41
66
|
* @example
|
|
42
67
|
* ```ts
|
|
43
68
|
* const base = new PromptBuilder().use(a).use(b);
|
|
44
|
-
* const variant = base.fork().use(c);
|
|
69
|
+
* const variant = base.fork().use(c);
|
|
45
70
|
* ```
|
|
46
71
|
*/
|
|
47
72
|
fork() {
|
|
48
73
|
const forked = new _PromptBuilder();
|
|
49
|
-
forked.
|
|
74
|
+
forked.nodes = deepCopy(this.nodes);
|
|
50
75
|
return forked;
|
|
51
76
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
*
|
|
55
|
-
* 1. Filters out sections whose `when` guard returns false
|
|
56
|
-
* 2. Renders each section
|
|
57
|
-
* 3. Filters out empty strings
|
|
58
|
-
* 4. Joins with separator and trims
|
|
59
|
-
*/
|
|
60
|
-
build(ctx) {
|
|
61
|
-
return this.buildWithMeta(ctx).prompt;
|
|
77
|
+
build(ctx, options) {
|
|
78
|
+
return this.buildWithMeta(ctx, options).prompt;
|
|
62
79
|
}
|
|
63
|
-
/**
|
|
64
|
-
|
|
65
|
-
* included/excluded. Useful for debugging and logging.
|
|
66
|
-
*
|
|
67
|
-
* A section is "excluded" if its `when` guard returns false.
|
|
68
|
-
* A section is "included" only if it passes the guard and renders
|
|
69
|
-
* a non-empty string.
|
|
70
|
-
*/
|
|
71
|
-
buildWithMeta(ctx) {
|
|
80
|
+
/** Like `build`, but also returns which section ids were included/excluded. */
|
|
81
|
+
buildWithMeta(ctx, options) {
|
|
72
82
|
const included = [];
|
|
73
83
|
const excluded = [];
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
84
|
+
const format = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _ => _.format]), () => ( "text"));
|
|
85
|
+
const parts = renderNodes(this.nodes, ctx, format, included, excluded);
|
|
86
|
+
const prompt = parts.join("\n\n").trim();
|
|
87
|
+
return { prompt, included, excluded };
|
|
88
|
+
}
|
|
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);
|
|
112
|
+
}
|
|
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)) {
|
|
128
|
+
excluded.push(section2.id);
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
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;
|
|
136
|
+
}
|
|
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;
|
|
146
|
+
}
|
|
147
|
+
if (renderSection(candidate, ctx, format, parts, included, excluded)) {
|
|
148
|
+
found = true;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
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));
|
|
78
161
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
162
|
+
} else {
|
|
163
|
+
renderOneOf(node, ctx, format, parts, included, excluded);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
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;
|
|
86
178
|
}
|
|
87
|
-
|
|
88
|
-
}).filter(Boolean).join("\n\n").trim();
|
|
89
|
-
return { prompt: rendered, included, excluded };
|
|
179
|
+
}
|
|
90
180
|
}
|
|
91
|
-
|
|
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 });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
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
|
+
}
|
|
212
|
+
}
|
|
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
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/section.ts
|
|
228
|
+
function section(id, render) {
|
|
229
|
+
return {
|
|
230
|
+
id,
|
|
231
|
+
render: (ctx) => _nullishCoalesce(render(ctx), () => ( ""))
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
92
235
|
|
|
93
236
|
|
|
94
|
-
exports.PromptBuilder = PromptBuilder;
|
|
237
|
+
exports.PromptBuilder = PromptBuilder; exports.section = section;
|
|
95
238
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/asurve/dev/teleprompt/dist/index.cjs","../src/builder.ts"],"names":[],"mappings":"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","file":"/Users/asurve/dev/teleprompt/dist/index.cjs","sourcesContent":[null,"import type { PromptContext, PromptSection } from './types';\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<\n TCtx extends PromptContext<Record<string, boolean>, Record<string, unknown>>,\n> {\n private sections: PromptSection<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 existingIdx = this.sections.findIndex((s) => s.id === section.id);\n if (existingIdx >= 0) {\n this.sections[existingIdx] = section;\n } else {\n this.sections.push(section);\n }\n return this;\n }\n\n /** Remove a section. Accepts an id string or a section object. */\n without(ref: string | { id: string }): this {\n const id = typeof ref === 'string' ? ref : ref.id;\n this.sections = this.sections.filter((s) => s.id !== id);\n return this;\n }\n\n /** Check if a section exists. Accepts an id string or a section object. */\n has(ref: string | { id: string }): boolean {\n const id = typeof ref === 'string' ? ref : ref.id;\n return this.sections.some((s) => s.id === id);\n }\n\n /** Get the ids of all registered sections (in insertion order). */\n ids(): string[] {\n return this.sections.map((s) => s.id);\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.sections = [...this.sections];\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 build(ctx: TCtx): string {\n return this.buildWithMeta(ctx).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(ctx: TCtx): {\n prompt: string;\n included: string[];\n excluded: string[];\n } {\n const included: string[] = [];\n const excluded: string[] = [];\n\n const rendered = this.sections\n .filter((s) => {\n if (s.when && !s.when(ctx)) {\n excluded.push(s.id);\n return false;\n }\n return true;\n })\n .map((s) => {\n const output = s.render(ctx);\n if (output) {\n included.push(s.id);\n } else {\n excluded.push(s.id);\n }\n return output;\n })\n .filter(Boolean)\n .join('\\n\\n')\n .trim();\n\n return { prompt: rendered, included, excluded };\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"]}
|