@tstdl/base 0.93.132 → 0.93.134
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/ai/prompts/index.d.ts +1 -0
- package/ai/prompts/index.js +1 -0
- package/ai/prompts/instructions-formatter.js +34 -6
- package/ai/prompts/prompt-builder.d.ts +24 -0
- package/ai/prompts/prompt-builder.js +115 -0
- package/examples/ai/prompt-builder.d.ts +1 -0
- package/examples/ai/prompt-builder.js +107 -0
- package/package.json +3 -3
- package/test5.js +5 -2
package/ai/prompts/index.d.ts
CHANGED
package/ai/prompts/index.js
CHANGED
|
@@ -35,6 +35,34 @@ function getPrefix(style, index, sectionDepth) {
|
|
|
35
35
|
return '';
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
+
function dedent(text) {
|
|
39
|
+
const lines = text.split('\n');
|
|
40
|
+
if (lines.length <= 1) {
|
|
41
|
+
return text.trim();
|
|
42
|
+
}
|
|
43
|
+
// Remove leading empty line if any (common in template literals)
|
|
44
|
+
if (lines[0].trim().length == 0) {
|
|
45
|
+
lines.shift();
|
|
46
|
+
}
|
|
47
|
+
// Remove trailing empty line if any
|
|
48
|
+
if (lines.length > 0 && lines[lines.length - 1].trim().length == 0) {
|
|
49
|
+
lines.pop();
|
|
50
|
+
}
|
|
51
|
+
if (lines.length == 0) {
|
|
52
|
+
return '';
|
|
53
|
+
}
|
|
54
|
+
// Find minimum indentation (excluding empty lines)
|
|
55
|
+
const minIndent = lines
|
|
56
|
+
.filter((line) => line.trim().length > 0)
|
|
57
|
+
.reduce((min, line) => {
|
|
58
|
+
const match = /^\s*/.exec(line);
|
|
59
|
+
return Math.min(min, match ? match[0].length : 0);
|
|
60
|
+
}, Infinity);
|
|
61
|
+
if (minIndent == Infinity) {
|
|
62
|
+
return lines.join('\n').trim();
|
|
63
|
+
}
|
|
64
|
+
return lines.map((line) => line.slice(minIndent)).join('\n').trim();
|
|
65
|
+
}
|
|
38
66
|
/**
|
|
39
67
|
* Formats a string that might span multiple lines.
|
|
40
68
|
* It calculates the hanging indent based on the length of the prefix used on the first line.
|
|
@@ -63,7 +91,7 @@ function processNode(node, context) {
|
|
|
63
91
|
if (isString(node)) {
|
|
64
92
|
const isSection = context.style == 'sections';
|
|
65
93
|
const currentBaseIndent = ' '.repeat(isSection ? 0 : context.indentDepth * INDENT_SIZE);
|
|
66
|
-
return formatWithHangingIndent(node, currentBaseIndent, '');
|
|
94
|
+
return formatWithHangingIndent(dedent(node), currentBaseIndent, '');
|
|
67
95
|
}
|
|
68
96
|
// 1. Unwrap InstructionsList (The Wrapper)
|
|
69
97
|
// If the node is a wrapper, we adopt its style and instruction, then process its items.
|
|
@@ -72,7 +100,7 @@ function processNode(node, context) {
|
|
|
72
100
|
// If this Wrapper is a root node, the instruction is usually printed by the caller or ignored.
|
|
73
101
|
// If this Wrapper is a value of a key, the key printer handles the instruction.
|
|
74
102
|
// However, if we have a "Root Wrapper" with an instruction (rare in your example), handle it:
|
|
75
|
-
const header = node.instruction ? `${node.instruction
|
|
103
|
+
const header = node.instruction ? `${dedent(node.instruction)}\n\n` : '';
|
|
76
104
|
// Determine next context
|
|
77
105
|
const nextContext = {
|
|
78
106
|
indentDepth: context.indentDepth, // Wrappers don't indent themselves, their content does
|
|
@@ -90,7 +118,7 @@ function processNode(node, context) {
|
|
|
90
118
|
return node.map((item, index) => {
|
|
91
119
|
const prefix = getPrefix(context.style, index, context.sectionDepth);
|
|
92
120
|
if (isString(item)) {
|
|
93
|
-
return formatWithHangingIndent(item, currentBaseIndent, prefix);
|
|
121
|
+
return formatWithHangingIndent(dedent(item), currentBaseIndent, prefix);
|
|
94
122
|
}
|
|
95
123
|
return processNode(item, context);
|
|
96
124
|
}).join(separator);
|
|
@@ -102,7 +130,7 @@ function processNode(node, context) {
|
|
|
102
130
|
// This allows us to pull the wrapper's "instruction" up to the Key line.
|
|
103
131
|
const isValueWrapper = isInstructionsList(value);
|
|
104
132
|
const effectiveValue = isValueWrapper ? value.items : value;
|
|
105
|
-
const instruction = (isValueWrapper && isDefined(value.instruction)) ? ` ${value.instruction}` : '';
|
|
133
|
+
const instruction = (isValueWrapper && isDefined(value.instruction)) ? ` ${dedent(value.instruction)}` : '';
|
|
106
134
|
const childStyle = isValueWrapper ? value.style : (isSection ? 'unordered' : 'unordered');
|
|
107
135
|
// Formatting the Header Line (The Key)
|
|
108
136
|
let headerLine = '';
|
|
@@ -132,14 +160,14 @@ function processNode(node, context) {
|
|
|
132
160
|
// or a new paragraph for Sections.
|
|
133
161
|
if (isSection) {
|
|
134
162
|
// Section: Header \n\n Content
|
|
135
|
-
return `${headerLine}\n\n${effectiveValue
|
|
163
|
+
return `${headerLine}\n\n${dedent(effectiveValue)}`;
|
|
136
164
|
}
|
|
137
165
|
else {
|
|
138
166
|
// List: "- **Key:** Value"
|
|
139
167
|
// We need to construct the full string to calculate hanging indent correctly.
|
|
140
168
|
// headerLine already contains indentation + prefix + key.
|
|
141
169
|
// We strip the indentation to feed it into the formatting helper effectively.
|
|
142
|
-
const fullLine = `${headerLine} ${effectiveValue}`.trim();
|
|
170
|
+
const fullLine = `${headerLine} ${dedent(effectiveValue)}`.trim();
|
|
143
171
|
return formatWithHangingIndent(fullLine, currentBaseIndent, '');
|
|
144
172
|
}
|
|
145
173
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Part } from 'genkit';
|
|
2
|
+
import type { ObjectLiteral } from '../../types/index.js';
|
|
3
|
+
import { type Instructions } from './instructions-formatter.js';
|
|
4
|
+
export type PromptBuilderInstructions = Record<string, Instructions>;
|
|
5
|
+
export type PromptBuilderContext = Record<string, PromptBuilderContextItem>;
|
|
6
|
+
export type PromptBuilderContextItem = ObjectLiteral;
|
|
7
|
+
export declare class PromptBuilder {
|
|
8
|
+
#private;
|
|
9
|
+
setSystemRole(role: string): this;
|
|
10
|
+
setRole(role: string): this;
|
|
11
|
+
setSystemTask(task: string): this;
|
|
12
|
+
setTask(task: string): this;
|
|
13
|
+
addSystemMedia(content: Uint8Array, mimeType: string): this;
|
|
14
|
+
addMedia(content: Uint8Array, mimeType: string): this;
|
|
15
|
+
addSystemInstructions(instructions: Record<string, Instructions>): this;
|
|
16
|
+
addInstructions(instructions: Record<string, Instructions>): this;
|
|
17
|
+
addSystemContext(title: string, context: PromptBuilderContextItem): this;
|
|
18
|
+
addSystemContext(context: PromptBuilderContext): this;
|
|
19
|
+
addContext(title: string, context: PromptBuilderContextItem): this;
|
|
20
|
+
addContext(context: PromptBuilderContext): this;
|
|
21
|
+
buildSystemPrompt(): Part[];
|
|
22
|
+
buildUserPrompt(): Part[];
|
|
23
|
+
}
|
|
24
|
+
export declare function promptBuilder(): PromptBuilder;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { encodeBase64 } from '../../utils/base64.js';
|
|
2
|
+
import { fromEntries, objectEntries, objectKeys } from '../../utils/object/index.js';
|
|
3
|
+
import { assertObjectPass, isDefined, isString } from '../../utils/type-guards.js';
|
|
4
|
+
import { formatData } from './format.js';
|
|
5
|
+
import { formatInstructions, sections } from './instructions-formatter.js';
|
|
6
|
+
export class PromptBuilder {
|
|
7
|
+
#systemRole;
|
|
8
|
+
#role;
|
|
9
|
+
#systemTask;
|
|
10
|
+
#task;
|
|
11
|
+
#systemInstructions = {};
|
|
12
|
+
#instructions = {};
|
|
13
|
+
#systemContextParts = {};
|
|
14
|
+
#contextParts = {};
|
|
15
|
+
#systemMedia = [];
|
|
16
|
+
#media = [];
|
|
17
|
+
setSystemRole(role) {
|
|
18
|
+
this.#systemRole = role;
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
21
|
+
setRole(role) {
|
|
22
|
+
this.#role = role;
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
setSystemTask(task) {
|
|
26
|
+
this.#systemTask = task;
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
setTask(task) {
|
|
30
|
+
this.#task = task;
|
|
31
|
+
return this;
|
|
32
|
+
}
|
|
33
|
+
addSystemMedia(content, mimeType) {
|
|
34
|
+
addMedia(content, mimeType, this.#systemMedia);
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
addMedia(content, mimeType) {
|
|
38
|
+
addMedia(content, mimeType, this.#media);
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
addSystemInstructions(instructions) {
|
|
42
|
+
this.#systemInstructions = { ...this.#systemInstructions, ...instructions };
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
addInstructions(instructions) {
|
|
46
|
+
this.#instructions = { ...this.#instructions, ...instructions };
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
addSystemContext(titleOrContext, contextOrNothing) {
|
|
50
|
+
if ((arguments.length == 1)) {
|
|
51
|
+
this.#systemContextParts = { ...this.#systemContextParts, ...assertObjectPass(titleOrContext) };
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
this.#systemContextParts[titleOrContext] = contextOrNothing;
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
addContext(titleOrContext, contextOrNothing) {
|
|
58
|
+
if ((arguments.length == 1)) {
|
|
59
|
+
this.#contextParts = { ...this.#contextParts, ...assertObjectPass(titleOrContext) };
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
this.#contextParts[titleOrContext] = contextOrNothing;
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
buildSystemPrompt() {
|
|
66
|
+
return buildPrompt({
|
|
67
|
+
role: this.#systemRole,
|
|
68
|
+
context: this.#systemContextParts,
|
|
69
|
+
instructions: this.#systemInstructions,
|
|
70
|
+
task: this.#systemTask,
|
|
71
|
+
media: this.#systemMedia,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
buildUserPrompt() {
|
|
75
|
+
return buildPrompt({
|
|
76
|
+
role: this.#role,
|
|
77
|
+
context: this.#contextParts,
|
|
78
|
+
instructions: this.#instructions,
|
|
79
|
+
task: this.#task,
|
|
80
|
+
media: this.#media,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export function promptBuilder() {
|
|
85
|
+
return new PromptBuilder();
|
|
86
|
+
}
|
|
87
|
+
function addMedia(content, mimeType, targetArray) {
|
|
88
|
+
const base64Data = encodeBase64(content);
|
|
89
|
+
const dataUrl = `data:${mimeType};base64,${base64Data}`;
|
|
90
|
+
targetArray.push({ media: { url: dataUrl } });
|
|
91
|
+
}
|
|
92
|
+
function buildPrompt(data) {
|
|
93
|
+
const instructions = {};
|
|
94
|
+
if (isDefined(data.role)) {
|
|
95
|
+
instructions['**Role**'] = data.role;
|
|
96
|
+
}
|
|
97
|
+
if (isDefined(data.context) && objectKeys(data.context).length > 0) {
|
|
98
|
+
const contextEntries = objectEntries(data.context).map(([key, contextPart]) => {
|
|
99
|
+
const string = isString(contextPart) ? contextPart : `\`\`\`toon\n${formatData(contextPart)}\n\`\`\``;
|
|
100
|
+
return [key, string];
|
|
101
|
+
});
|
|
102
|
+
instructions['**Context**'] = sections(fromEntries(contextEntries));
|
|
103
|
+
}
|
|
104
|
+
if (isDefined(data.instructions) && (objectKeys(data.instructions).length > 0)) {
|
|
105
|
+
instructions['**Instructions**'] = data.instructions;
|
|
106
|
+
}
|
|
107
|
+
if (isDefined(data.task)) {
|
|
108
|
+
instructions['**Task**'] = data.task;
|
|
109
|
+
}
|
|
110
|
+
const formattedInstructions = formatInstructions(instructions);
|
|
111
|
+
return [
|
|
112
|
+
...(data.media ?? []),
|
|
113
|
+
{ text: formattedInstructions },
|
|
114
|
+
];
|
|
115
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '../../polyfills.js';
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import '../../polyfills.js';
|
|
2
|
+
import { orderedList, promptBuilder, unorderedList } from '../../ai/index.js';
|
|
3
|
+
import { Application } from '../../application/application.js';
|
|
4
|
+
import { provideModule, provideSignalHandler } from '../../application/index.js';
|
|
5
|
+
import { PrettyPrintLogFormatter } from '../../logger/index.js';
|
|
6
|
+
import { provideConsoleLogTransport } from '../../logger/transports/console.js';
|
|
7
|
+
async function main() {
|
|
8
|
+
const builder = promptBuilder();
|
|
9
|
+
// 1. Configure System Instructions
|
|
10
|
+
// System instructions define the persona and fundamental rules of the AI.
|
|
11
|
+
builder
|
|
12
|
+
.setSystemRole('A world-class senior software architect and mentor.')
|
|
13
|
+
.setSystemTask('Review the provided code for architectural flaws, performance bottlenecks, and security vulnerabilities.')
|
|
14
|
+
.addSystemContext('Project Architecture', {
|
|
15
|
+
type: 'Monorepo',
|
|
16
|
+
language: 'TypeScript',
|
|
17
|
+
framework: 'tstdl (Custom Framework)',
|
|
18
|
+
modules: ['API', 'ORM', 'Injector', 'Logger'],
|
|
19
|
+
})
|
|
20
|
+
.addSystemContext('Environment', {
|
|
21
|
+
nodeVersion: process.version,
|
|
22
|
+
platform: process.platform,
|
|
23
|
+
memoryLimit: '4GB',
|
|
24
|
+
})
|
|
25
|
+
.addSystemInstructions({
|
|
26
|
+
'Output Style': unorderedList('Ensure the output follows these structural rules:', {
|
|
27
|
+
Language: 'Always respond in English.',
|
|
28
|
+
Tone: 'Professional, concise, and constructive.',
|
|
29
|
+
Format: orderedList('Structure your response as follows:', [
|
|
30
|
+
'Executive Summary',
|
|
31
|
+
'Detailed Findings (categorized by severity)',
|
|
32
|
+
'Code Improvements (with examples)',
|
|
33
|
+
'Next Steps',
|
|
34
|
+
]),
|
|
35
|
+
}),
|
|
36
|
+
'Formatting rules': orderedList('Follow these rules for code formatting:', {
|
|
37
|
+
Indentation: 'Use 2 spaces.',
|
|
38
|
+
Semicolons: 'Always use semicolons.',
|
|
39
|
+
Quotes: 'Use single quotes.',
|
|
40
|
+
}),
|
|
41
|
+
'Response Constraints': unorderedList('Must never include:', [
|
|
42
|
+
'Personal opinions.',
|
|
43
|
+
'Speculative performance metrics without data.',
|
|
44
|
+
'Unnecessary apologies.',
|
|
45
|
+
]),
|
|
46
|
+
'Safety Guidelines': unorderedList([
|
|
47
|
+
'Never suggest disabling security features.',
|
|
48
|
+
'Avoid recommending deprecated libraries.',
|
|
49
|
+
'Always prioritize type safety.',
|
|
50
|
+
]),
|
|
51
|
+
});
|
|
52
|
+
// 2. Configure User Prompt
|
|
53
|
+
// The prompt defines the specific request and context for the current interaction.
|
|
54
|
+
builder
|
|
55
|
+
.setRole('A developer seeking feedback on a new feature.')
|
|
56
|
+
.setTask('Please review this new User Service implementation.')
|
|
57
|
+
.addContext('Code to Review', `
|
|
58
|
+
@Singleton()
|
|
59
|
+
export class UserService extends getRepository(User) {
|
|
60
|
+
readonly #logger = inject(Logger, UserService.name);
|
|
61
|
+
|
|
62
|
+
async createUser(data: UserData): Promise<User> {
|
|
63
|
+
this.#logger.info('Creating user', { data });
|
|
64
|
+
return this.insert(data);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
`)
|
|
68
|
+
.addContext('Requirements', [
|
|
69
|
+
'Must use private class fields (#).',
|
|
70
|
+
'Must use the project logger.',
|
|
71
|
+
'Must extend the base repository.',
|
|
72
|
+
])
|
|
73
|
+
.addContext('Free Text User Hint', 'Focus on potential issues with dependency injection and logging practices.')
|
|
74
|
+
.addInstructions({
|
|
75
|
+
'Specific Focus': 'Pay special attention to the use of dependency injection and logging.',
|
|
76
|
+
});
|
|
77
|
+
// 3. Add Media (Simulated)
|
|
78
|
+
// Media can be added (e.g., screenshots of logs or diagrams).
|
|
79
|
+
const dummyImage = new Uint8Array([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
|
|
80
|
+
builder.addMedia(dummyImage, 'image/png');
|
|
81
|
+
// 4. Build and Display
|
|
82
|
+
const systemMessage = builder.buildSystemPrompt();
|
|
83
|
+
const userMessage = builder.buildUserPrompt();
|
|
84
|
+
console.log('--- SYSTEM MESSAGE ---');
|
|
85
|
+
for (const part of systemMessage) {
|
|
86
|
+
if (part.text) {
|
|
87
|
+
console.log(part.text);
|
|
88
|
+
}
|
|
89
|
+
if (part.media) {
|
|
90
|
+
console.log(`[Media: ${part.media.url}]`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
console.log('\n\n--- USER PROMPT ---');
|
|
94
|
+
for (const part of userMessage) {
|
|
95
|
+
if (part.text) {
|
|
96
|
+
console.log(part.text);
|
|
97
|
+
}
|
|
98
|
+
if (part.media) {
|
|
99
|
+
console.log(`[Media: ${part.media.url}]`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
Application.run('PromptBuilder Demo', [
|
|
104
|
+
provideConsoleLogTransport(PrettyPrintLogFormatter),
|
|
105
|
+
provideModule(main),
|
|
106
|
+
provideSignalHandler(),
|
|
107
|
+
]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tstdl/base",
|
|
3
|
-
"version": "0.93.
|
|
3
|
+
"version": "0.93.134",
|
|
4
4
|
"author": "Patrick Hein",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -162,8 +162,8 @@
|
|
|
162
162
|
"file-type": "^21.3",
|
|
163
163
|
"genkit": "^1.28",
|
|
164
164
|
"handlebars": "^4.7",
|
|
165
|
-
"@aws-sdk/client-s3": "^3.
|
|
166
|
-
"@aws-sdk/s3-request-presigner": "^3.
|
|
165
|
+
"@aws-sdk/client-s3": "^3.992",
|
|
166
|
+
"@aws-sdk/s3-request-presigner": "^3.992",
|
|
167
167
|
"mjml": "^4.18",
|
|
168
168
|
"nodemailer": "^8.0",
|
|
169
169
|
"pg": "^8.18",
|
package/test5.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import './polyfills.js';
|
|
2
|
+
import { promptBuilder } from './ai/index.js';
|
|
2
3
|
import { Application } from './application/application.js';
|
|
3
4
|
import { provideModule, provideSignalHandler } from './application/index.js';
|
|
4
5
|
import { PrettyPrintLogFormatter } from './logger/index.js';
|
|
5
6
|
import { provideConsoleLogTransport } from './logger/transports/console.js';
|
|
6
|
-
import { mergePdfs } from './pdf/utils.js';
|
|
7
7
|
async function main(_cancellationSignal) {
|
|
8
|
-
|
|
8
|
+
const x = promptBuilder();
|
|
9
|
+
console.log(x.buildSystemPrompt()[0].text);
|
|
10
|
+
console.log('\n\n---\n\n');
|
|
11
|
+
console.log(x.buildUserPrompt()[0].text);
|
|
9
12
|
}
|
|
10
13
|
Application.run('Test', [
|
|
11
14
|
provideConsoleLogTransport(PrettyPrintLogFormatter),
|