@ebowwa/ai 0.1.0 → 0.1.1

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/prompts.ts ADDED
@@ -0,0 +1,551 @@
1
+ /**
2
+ * Composable prompt architecture for scalable AI interactions
3
+ *
4
+ * Core concepts:
5
+ * - PromptPart: Composable building blocks
6
+ * - PromptBuilder: Fluent API for assembling prompts
7
+ * - PromptTemplate: Reusable templates with interpolation
8
+ * - PromptChain: Multi-step workflows
9
+ * - PromptStrategy: Different reasoning approaches
10
+ */
11
+
12
+ // Import and re-export ChatMessage for type compatibility
13
+ import type { ChatMessage } from "./types.js";
14
+
15
+ export type { ChatMessage };
16
+
17
+ /**
18
+ * Base interface for all composable prompt parts
19
+ */
20
+ export interface PromptPart {
21
+ toPrompt(): string;
22
+ }
23
+
24
+ /**
25
+ * System instruction - sets AI role and behavior
26
+ */
27
+ export class SystemInstruction implements PromptPart {
28
+ constructor(
29
+ public role: string,
30
+ public guidelines?: string[],
31
+ ) {}
32
+
33
+ toPrompt(): string {
34
+ const base = `You are a ${this.role}.`;
35
+ if (this.guidelines?.length) {
36
+ return `${base}\n\nGuidelines:\n${this.guidelines.map((g) => `- ${g}`).join("\n")}`;
37
+ }
38
+ return base;
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Context - provides data/background information
44
+ */
45
+ export class Context implements PromptPart {
46
+ constructor(public data: Record<string, unknown>) {}
47
+
48
+ toPrompt(): string {
49
+ const entries = Object.entries(this.data)
50
+ .filter(([_, v]) => v !== undefined && v !== null && v !== "")
51
+ .map(
52
+ ([k, v]) => `- ${k}: ${typeof v === "object" ? JSON.stringify(v) : v}`,
53
+ )
54
+ .join("\n");
55
+ return entries ? `Context:\n${entries}` : "";
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Examples - few-shot learning samples
61
+ */
62
+ export class Examples implements PromptPart {
63
+ constructor(public examples: Array<{ input: string; output: string }>) {}
64
+
65
+ toPrompt(): string {
66
+ if (this.examples.length === 0) return "";
67
+ return this.examples
68
+ .map(
69
+ (ex, i) =>
70
+ `Example ${i + 1}:\nInput: ${ex.input}\nOutput: ${ex.output}`,
71
+ )
72
+ .join("\n\n");
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Output format specification
78
+ */
79
+ export class OutputFormat implements PromptPart {
80
+ constructor(
81
+ public type: "json" | "text" | "code" | "markdown",
82
+ public schema?: Record<string, unknown>,
83
+ ) {}
84
+
85
+ toPrompt(): string {
86
+ if (this.type === "json" && this.schema) {
87
+ return `Respond in JSON format with this schema:\n${JSON.stringify(this.schema, null, 2)}`;
88
+ }
89
+ return `Respond in ${this.type} format.`;
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Constraints - rules and limitations
95
+ */
96
+ export class Constraints implements PromptPart {
97
+ constructor(public rules: string[]) {}
98
+
99
+ toPrompt(): string {
100
+ if (this.rules.length === 0) return "";
101
+ return `Constraints:\n${this.rules.map((r) => `- ${r}`).join("\n")}`;
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Task - the main instruction/question
107
+ */
108
+ export class Task implements PromptPart {
109
+ constructor(public instruction: string) {}
110
+
111
+ toPrompt(): string {
112
+ return `Task:\n${this.instruction}`;
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Fluent builder for composable prompts
118
+ */
119
+ export class PromptBuilder {
120
+ private parts: PromptPart[] = [];
121
+ private systemPart?: SystemInstruction;
122
+
123
+ /**
124
+ * Set system role and guidelines
125
+ */
126
+ system(role: string, guidelines?: string[]): this {
127
+ this.systemPart = new SystemInstruction(role, guidelines);
128
+ return this;
129
+ }
130
+
131
+ /**
132
+ * Add context data
133
+ */
134
+ context(data: Record<string, unknown>): this {
135
+ this.parts.push(new Context(data));
136
+ return this;
137
+ }
138
+
139
+ /**
140
+ * Add few-shot examples
141
+ */
142
+ examples(examples: Array<{ input: string; output: string }>): this {
143
+ this.parts.push(new Examples(examples));
144
+ return this;
145
+ }
146
+
147
+ /**
148
+ * Specify output format
149
+ */
150
+ output(
151
+ type: "json" | "text" | "code" | "markdown",
152
+ schema?: Record<string, unknown>,
153
+ ): this {
154
+ this.parts.push(new OutputFormat(type, schema));
155
+ return this;
156
+ }
157
+
158
+ /**
159
+ * Add constraints/rules
160
+ */
161
+ constraints(...rules: string[]): this {
162
+ this.parts.push(new Constraints(rules));
163
+ return this;
164
+ }
165
+
166
+ /**
167
+ * Add the main task/instruction
168
+ */
169
+ task(instruction: string): this {
170
+ this.parts.push(new Task(instruction));
171
+ return this;
172
+ }
173
+
174
+ /**
175
+ * Add custom prompt part
176
+ */
177
+ custom(part: PromptPart): this {
178
+ this.parts.push(part);
179
+ return this;
180
+ }
181
+
182
+ /**
183
+ * Build final prompt string (for simple generate)
184
+ */
185
+ build(): string {
186
+ const allParts = [
187
+ ...(this.systemPart ? [this.systemPart] : []),
188
+ ...this.parts,
189
+ ];
190
+ return allParts
191
+ .map((p) => p.toPrompt())
192
+ .filter(Boolean)
193
+ .join("\n\n");
194
+ }
195
+
196
+ /**
197
+ * Build for chat completion (returns messages array)
198
+ */
199
+ buildChat(): ChatMessage[] {
200
+ const messages: ChatMessage[] = [];
201
+
202
+ if (this.systemPart) {
203
+ messages.push({ role: "system", content: this.systemPart.toPrompt() });
204
+ }
205
+
206
+ const userContent = this.parts
207
+ .map((p) => p.toPrompt())
208
+ .filter(Boolean)
209
+ .join("\n\n");
210
+ if (userContent) {
211
+ messages.push({ role: "user", content: userContent });
212
+ }
213
+
214
+ return messages;
215
+ }
216
+
217
+ /**
218
+ * Build as system + user prompt pair
219
+ */
220
+ buildPair(): { system: string; user: string } {
221
+ return {
222
+ system: this.systemPart?.toPrompt() || "",
223
+ user: this.parts
224
+ .map((p) => p.toPrompt())
225
+ .filter(Boolean)
226
+ .join("\n\n"),
227
+ };
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Reusable prompt template with variable interpolation
233
+ */
234
+ export class PromptTemplate {
235
+ constructor(private template: string) {}
236
+
237
+ /**
238
+ * Render template with variables
239
+ * Supports {{variable}} syntax
240
+ */
241
+ render(vars: Record<string, unknown>): string {
242
+ let result = this.template;
243
+ for (const [key, value] of Object.entries(vars)) {
244
+ const placeholder = `{{${key}}}`;
245
+ result = result.replaceAll(placeholder, String(value ?? ""));
246
+ }
247
+ return result;
248
+ }
249
+
250
+ /**
251
+ * Create template from string
252
+ */
253
+ static from(template: string): PromptTemplate {
254
+ return new PromptTemplate(template);
255
+ }
256
+
257
+ /**
258
+ * Create typed template with compile-time safety
259
+ */
260
+ static typed<T extends Record<string, unknown>>(
261
+ template: string,
262
+ ): TypedPromptTemplate<T> {
263
+ return new TypedPromptTemplate(template);
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Type-safe prompt template
269
+ */
270
+ export class TypedPromptTemplate<T extends Record<string, unknown>> {
271
+ constructor(private template: string) {}
272
+
273
+ render(vars: T): string {
274
+ return new PromptTemplate(this.template).render(vars);
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Prompt strategies for different reasoning approaches
280
+ */
281
+ export class PromptStrategy {
282
+ /**
283
+ * Zero-shot: direct instruction without examples
284
+ */
285
+ static zeroShot(instruction: string): string {
286
+ return instruction;
287
+ }
288
+
289
+ /**
290
+ * Few-shot: instruction with examples
291
+ */
292
+ static fewShot(
293
+ instruction: string,
294
+ examples: Array<{ input: string; output: string }>,
295
+ ): string {
296
+ return new PromptBuilder().examples(examples).task(instruction).build();
297
+ }
298
+
299
+ /**
300
+ * Chain-of-thought: step-by-step reasoning
301
+ */
302
+ static chainOfThought(question: string): string {
303
+ return `${question}\n\nThink step by step. Show your work and reasoning process.`;
304
+ }
305
+
306
+ /**
307
+ * ReAct: Reasoning + Acting (for tool use)
308
+ */
309
+ static reAct(task: string): string {
310
+ return `Task: ${task}
311
+
312
+ Think: [your reasoning about the current state]
313
+ Act: [action to take]
314
+ Observation: [result of action]
315
+
316
+ Repeat Think/Act/Observation until the task is complete.`;
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Chain prompts where output feeds into next input
322
+ */
323
+ export class PromptChain {
324
+ private steps: Array<(input: string, client: any) => Promise<string>> = [];
325
+
326
+ /**
327
+ * Add a step to the chain
328
+ */
329
+ add(step: (input: string, client: any) => Promise<string>): this {
330
+ this.steps.push(step);
331
+ return this;
332
+ }
333
+
334
+ /**
335
+ * Execute the entire chain
336
+ */
337
+ async execute(initialInput: string, client: any): Promise<string[]> {
338
+ const results: string[] = [];
339
+ let current = initialInput;
340
+
341
+ for (const step of this.steps) {
342
+ current = await step(current, client);
343
+ results.push(current);
344
+ }
345
+
346
+ return results;
347
+ }
348
+ }
349
+
350
+ // ============================================================================
351
+ // PRE-BUILT PROMPTS (Refactored using composable architecture)
352
+ // ============================================================================
353
+
354
+ /**
355
+ * Pre-built prompt templates for common AI tasks
356
+ */
357
+ export const PROMPTS = {
358
+ /**
359
+ * Generate a server name based on project context
360
+ */
361
+ generateServerName: (project?: string, description?: string) => {
362
+ return new PromptBuilder()
363
+ .system("server naming specialist", [
364
+ "Create memorable, technical names",
365
+ "Use lowercase letters and hyphens only",
366
+ ])
367
+ .context({ project, description })
368
+ .constraints(
369
+ "8-15 characters maximum",
370
+ "lowercase letters only",
371
+ "hyphens allowed but no consecutive hyphens",
372
+ "Return ONLY the name, no explanation",
373
+ )
374
+ .task("Generate a short, memorable server name.")
375
+ .build();
376
+ },
377
+
378
+ /**
379
+ * Suggest optimal server type based on workload
380
+ */
381
+ suggestServerType: (workload: string) => {
382
+ return new PromptBuilder()
383
+ .system("Hetzner cloud infrastructure expert", [
384
+ "Know all Hetzner server types and their capabilities",
385
+ "Consider cost-effectiveness",
386
+ ])
387
+ .context({ workload })
388
+ .examples([
389
+ {
390
+ input: "High-traffic web server with moderate CPU needs",
391
+ output:
392
+ "cpx21 - Good balance of performance and cost for web workloads",
393
+ },
394
+ {
395
+ input: "Development/testing server",
396
+ output: "cpx11 - Lowest cost option, sufficient for development",
397
+ },
398
+ ])
399
+ .constraints(
400
+ "Respond with just the server type name",
401
+ "Follow with one brief sentence explaining why",
402
+ "Suggest any appropriate Hetzner server type based on the workload needs",
403
+ )
404
+ .task("Suggest the best Hetzner server type for this workload.")
405
+ .build();
406
+ },
407
+
408
+ /**
409
+ * Analyze resource usage and provide recommendations
410
+ */
411
+ analyzeResources: (
412
+ cpu: number,
413
+ memory: number,
414
+ disk: number,
415
+ activePorts?: string[],
416
+ ) => {
417
+ const contextData: Record<string, unknown> = {
418
+ cpu: `${cpu}%`,
419
+ memory: `${memory}%`,
420
+ disk: `${disk}%`,
421
+ };
422
+
423
+ if (activePorts && activePorts.length > 0) {
424
+ contextData.activePorts =
425
+ activePorts.slice(0, 10).join(", ") +
426
+ (activePorts.length > 10 ? ` (+${activePorts.length - 10} more)` : "");
427
+ }
428
+
429
+ return new PromptBuilder()
430
+ .system("DevOps monitoring specialist", [
431
+ "Assess server health holistically",
432
+ "Prioritize actionable advice",
433
+ ])
434
+ .context(contextData)
435
+ .examples([
436
+ {
437
+ input: "CPU: 95%, Memory: 90%, Disk: 50%",
438
+ output:
439
+ "CRITICAL: Immediate scale-up required. CPU and memory are at dangerous levels.",
440
+ },
441
+ {
442
+ input: "CPU: 15%, Memory: 25%, Disk: 30%",
443
+ output: "HEALTHY: All metrics within normal range. No action needed.",
444
+ },
445
+ {
446
+ input: "CPU: 45%, Memory: 85%, Disk: 20%",
447
+ output:
448
+ "WARNING: Memory usage high. Consider optimizing applications or upgrading memory.",
449
+ },
450
+ ])
451
+ .constraints(
452
+ "Assessment must be one of: HEALTHY, WARNING, CRITICAL",
453
+ "Provide exactly one sentence for assessment",
454
+ "Provide exactly one actionable recommendation if not HEALTHY",
455
+ "Keep total response under 100 words",
456
+ )
457
+ .task("Analyze the server resource usage and provide health assessment.")
458
+ .build();
459
+ },
460
+
461
+ /**
462
+ * Generate SSH troubleshooting tips
463
+ */
464
+ sshTroubleshoot: (error: string) => {
465
+ return new PromptBuilder()
466
+ .system("SSH troubleshooting expert", [
467
+ "Know common SSH issues and solutions",
468
+ "Provide practical, step-by-step solutions",
469
+ ])
470
+ .context({ error })
471
+ .examples([
472
+ {
473
+ input: "Connection refused",
474
+ output:
475
+ "1. Verify SSH service is running: systemctl status sshd\n2. Check firewall rules\n3. Confirm correct port (usually 22)",
476
+ },
477
+ {
478
+ input: "Permission denied (publickey)",
479
+ output:
480
+ "1. Verify public key is added to server's ~/.ssh/authorized_keys\n2. Check file permissions: chmod 700 ~/.ssh and chmod 600 ~/.ssh/authorized_keys\n3. Ensure you're using the correct private key",
481
+ },
482
+ ])
483
+ .constraints(
484
+ "Provide 2-3 specific troubleshooting steps",
485
+ "Each step should be actionable",
486
+ "Keep responses concise",
487
+ )
488
+ .task("Provide SSH troubleshooting steps for this error.")
489
+ .build();
490
+ },
491
+
492
+ /**
493
+ * Suggest server actions based on state
494
+ */
495
+ suggestActions: (status: string, age?: string) => {
496
+ return new PromptBuilder()
497
+ .system("Server operations specialist", [
498
+ "Understand server lifecycle management",
499
+ "Consider cost implications",
500
+ ])
501
+ .context({ status, age })
502
+ .examples([
503
+ {
504
+ input: "Status: stopped, Age: 2 hours",
505
+ output:
506
+ "Actions: start (to resume service), delete (if no longer needed)",
507
+ },
508
+ {
509
+ input: "Status: running, Age: 30 days",
510
+ output:
511
+ "Actions: reboot (for maintenance), resize (if performance issues)",
512
+ },
513
+ ])
514
+ .constraints(
515
+ "Suggest 1-2 appropriate actions",
516
+ "Include very brief explanation for each",
517
+ "Consider actions: start, stop, restart, delete, resize",
518
+ )
519
+ .task("Suggest appropriate server management actions.")
520
+ .build();
521
+ },
522
+
523
+ /**
524
+ * Generate a witty server status message
525
+ */
526
+ statusMessage: (status: string, name: string) => {
527
+ return new PromptBuilder()
528
+ .system("Playful status message generator", [
529
+ "Be lighthearted but professional",
530
+ "Use puns and wordplay when appropriate",
531
+ ])
532
+ .context({ status, name })
533
+ .examples([
534
+ {
535
+ input: "Status: running, Name: prod-db-01",
536
+ output: "🟢 prod-db-01 is alive and kicking!",
537
+ },
538
+ {
539
+ input: "Status: stopped, Name: dev-server",
540
+ output: "💤 dev-server is taking a nap",
541
+ },
542
+ ])
543
+ .constraints(
544
+ "Keep under 50 characters",
545
+ "Use appropriate emoji for status",
546
+ "Be creative and memorable",
547
+ )
548
+ .task("Generate a brief, witty status message.")
549
+ .build();
550
+ },
551
+ } as const;
package/types.js ADDED
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ /**
3
+ * OpenAI protocol related types for the application
4
+ *
5
+ * This file now re-exports types from schema.ts for backward compatibility.
6
+ * New code should import directly from schema.ts.
7
+ *
8
+ * Schema provides:
9
+ * - Runtime validation with Zod
10
+ * - Type inference for TypeScript
11
+ * - Helper functions for validation
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.categorizeError = exports.convertUsage = exports.validateRawResponse = exports.validateChatCompletionOptions = exports.validateChatMessages = exports.validateChatMessage = void 0;
15
+ // Re-export validation helpers for convenience
16
+ var ai_1 = require("@ebowwa/codespaces-types/runtime/ai");
17
+ Object.defineProperty(exports, "validateChatMessage", { enumerable: true, get: function () { return ai_1.validateChatMessage; } });
18
+ Object.defineProperty(exports, "validateChatMessages", { enumerable: true, get: function () { return ai_1.validateChatMessages; } });
19
+ Object.defineProperty(exports, "validateChatCompletionOptions", { enumerable: true, get: function () { return ai_1.validateChatCompletionOptions; } });
20
+ Object.defineProperty(exports, "validateRawResponse", { enumerable: true, get: function () { return ai_1.validateRawResponse; } });
21
+ Object.defineProperty(exports, "convertUsage", { enumerable: true, get: function () { return ai_1.convertUsage; } });
22
+ Object.defineProperty(exports, "categorizeError", { enumerable: true, get: function () { return ai_1.categorizeError; } });
@@ -9,5 +9,16 @@
9
9
  * - Type inference for TypeScript
10
10
  * - Helper functions for validation
11
11
  */
12
+
13
+ // Re-export all types from schema
14
+ export type * from "@ebowwa/codespaces-types/runtime/ai";
15
+
12
16
  // Re-export validation helpers for convenience
13
- export { validateChatMessage, validateChatMessages, validateChatCompletionOptions, validateRawResponse, convertUsage, categorizeError, } from "./schemas/ai.js";
17
+ export {
18
+ validateChatMessage,
19
+ validateChatMessages,
20
+ validateChatCompletionOptions,
21
+ validateRawResponse,
22
+ convertUsage,
23
+ categorizeError,
24
+ } from "@ebowwa/codespaces-types/runtime/ai";
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 Ebowwa Labs
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.