aiwg
Version:
Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo
171 lines (144 loc) • 5.2 kB
text/typescript
/**
* Simple Chain Pipeline — generated by aiwg nlp new
* Pattern: simple-chain
* Dependencies: @anthropic-ai/sdk
*
* Install: npm install @anthropic-ai/sdk
*/
import Anthropic from "@anthropic-ai/sdk";
import * as fs from "fs";
import * as path from "path";
// ---------------------------------------------------------------------------
// Configuration
// ---------------------------------------------------------------------------
const PROMPTS_DIR = path.join(__dirname, "..", "prompts");
const MODEL = "claude-haiku-4-5";
const MAX_TOKENS = 512;
const TIMEOUT_MS = 30_000;
const MAX_RETRIES = 3;
const RETRY_ON_STATUS = new Set([429, 502, 503]);
const BACKOFF_MS = 1_000;
// ---------------------------------------------------------------------------
// Prompt loading
// ---------------------------------------------------------------------------
interface Prompt {
system: string;
user: string;
}
function loadPrompt(filename: string, variables: Record<string, string>): Prompt {
const filePath = path.join(PROMPTS_DIR, filename);
let content = fs.readFileSync(filePath, "utf-8");
// Strip YAML frontmatter
if (content.startsWith("---")) {
const parts = content.split("---");
content = parts.slice(2).join("---");
}
// Parse ## System and ## User sections
let system = "";
let user = "";
let currentSection: "system" | "user" | null = null;
for (const line of content.split("\n")) {
if (line.trim() === "## System") {
currentSection = "system";
} else if (line.trim() === "## User") {
currentSection = "user";
} else if (currentSection === "system") {
system += line + "\n";
} else if (currentSection === "user") {
user += line + "\n";
}
}
// Substitute variables
for (const [key, value] of Object.entries(variables)) {
system = system.replaceAll(`{{${key}}}`, value);
user = user.replaceAll(`{{${key}}}`, value);
}
return { system: system.trim(), user: user.trim() };
}
// ---------------------------------------------------------------------------
// LLM call with retry
// ---------------------------------------------------------------------------
async function callLLM(
client: Anthropic,
system: string,
user: string,
model: string = MODEL,
maxTokens: number = MAX_TOKENS,
): Promise<string> {
let lastError: Error | undefined;
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
try {
const response = await client.messages.create({
model,
max_tokens: maxTokens,
system,
messages: [{ role: "user", content: user }],
});
const block = response.content[0];
if (block.type !== "text") throw new Error("Unexpected content block type");
return block.text;
} catch (err: unknown) {
const status = (err as { status?: number }).status;
if (status !== undefined && RETRY_ON_STATUS.has(status)) {
lastError = err as Error;
await new Promise((resolve) => setTimeout(resolve, BACKOFF_MS * 2 ** attempt));
} else {
throw err;
}
}
}
throw new Error(`LLM call failed after ${MAX_RETRIES} attempts: ${lastError?.message}`);
}
// ---------------------------------------------------------------------------
// Output validation
// ---------------------------------------------------------------------------
function validateOutput<T>(raw: string): T {
let cleaned = raw.trim();
// Strip markdown code fences if present
if (cleaned.startsWith("```")) {
const lines = cleaned.split("\n");
cleaned = lines
.slice(1, lines[lines.length - 1] === "```" ? -1 : undefined)
.join("\n");
}
try {
return JSON.parse(cleaned) as T;
} catch (e) {
throw new Error(`Output is not valid JSON: ${e}\nRaw: ${cleaned.slice(0, 200)}`);
}
}
// ---------------------------------------------------------------------------
// Pipeline
// ---------------------------------------------------------------------------
interface PipelineOutput {
// TODO: define your output fields here
[key: string]: unknown;
}
export async function run(inputText: string): Promise<PipelineOutput> {
const client = new Anthropic({ timeout: TIMEOUT_MS });
// Step: extract
const { system, user } = loadPrompt("generator.prompt.md", { input_text: inputText });
const raw = await callLLM(client, system, user);
const output = validateOutput<PipelineOutput>(raw);
// Add additional steps here:
// const { system: s2, user: u2 } = loadPrompt("step2.prompt.md", { field: output.field });
// const raw2 = await callLLM(client, s2, u2);
// const output2 = validateOutput<Step2Output>(raw2);
return output;
}
// ---------------------------------------------------------------------------
// Entry point
// ---------------------------------------------------------------------------
if (require.main === module) {
const input = process.argv[2];
if (!input) {
console.error("Usage: ts-node pipeline.ts '<input text>'");
process.exit(1);
}
run(input)
.then((result) => console.log(JSON.stringify(result, null, 2)))
.catch((err) => {
console.error(err);
process.exit(1);
});
}