Add AI agent capabilities and integrate with Anthropic and LNbits

Integrate Anthropic AI for agent capabilities, introduce database schemas for jobs and invoices, and set up LNbits for payment processing.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: cce28acc-aeac-46ff-80ec-af4ade39e30f
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
alexpaynex
2026-03-18 14:59:02 +00:00
parent 90354c5034
commit b095efcfd3
20 changed files with 719 additions and 20 deletions

View File

@@ -0,0 +1,71 @@
import { anthropic } from "@workspace/integrations-anthropic-ai";
const EVAL_MODEL = "claude-haiku-4-5";
const WORK_MODEL = "claude-sonnet-4-6";
export interface EvalResult {
approved: boolean;
reason: string;
}
export async function evaluateRequest(request: string): Promise<EvalResult> {
const message = await anthropic.messages.create({
model: EVAL_MODEL,
max_tokens: 8192,
system: `You are Timmy, an AI agent gatekeeper. Your job is to evaluate user requests.
A request should be APPROVED if it is:
- Clear and specific enough to act on
- Ethical, lawful, and not harmful
- Within the capabilities of a general-purpose AI assistant
A request should be REJECTED if it is:
- Harmful, illegal, or unethical
- Completely incoherent or impossible to act on
- Spam or an attempt to abuse the system
Respond ONLY with valid JSON in this exact format:
{"approved": true, "reason": "Brief explanation"}
or
{"approved": false, "reason": "Brief explanation of why it was rejected"}`,
messages: [
{
role: "user",
content: `Evaluate this request: ${request}`,
},
],
});
const block = message.content[0];
if (block.type !== "text") {
throw new Error("Unexpected response type from eval model");
}
try {
const parsed = JSON.parse(block.text) as { approved: boolean; reason: string };
return { approved: Boolean(parsed.approved), reason: parsed.reason ?? "" };
} catch {
throw new Error(`Failed to parse eval response: ${block.text}`);
}
}
export async function executeRequest(request: string): Promise<string> {
const message = await anthropic.messages.create({
model: WORK_MODEL,
max_tokens: 8192,
system: `You are Timmy, a capable AI agent. A user has paid for you to handle their request.
Do your best to fulfill it thoroughly and helpfully. Be concise yet complete.`,
messages: [
{
role: "user",
content: request,
},
],
});
const block = message.content[0];
if (block.type !== "text") {
throw new Error("Unexpected response type from work model");
}
return block.text;
}

View File

@@ -0,0 +1,87 @@
const LNBITS_URL = process.env.LNBITS_URL;
const LNBITS_API_KEY = process.env.LNBITS_API_KEY;
function getBaseUrl(): string {
if (!LNBITS_URL) {
throw new Error("LNBITS_URL environment variable is not set");
}
return LNBITS_URL.replace(/\/$/, "");
}
function getHeaders(): Record<string, string> {
if (!LNBITS_API_KEY) {
throw new Error("LNBITS_API_KEY environment variable is not set");
}
return {
"Content-Type": "application/json",
"X-Api-Key": LNBITS_API_KEY,
};
}
export interface LNbitsInvoice {
paymentHash: string;
paymentRequest: string;
}
export interface LNbitsInvoiceStatus {
paid: boolean;
paidAt?: Date;
}
export async function createInvoice(
amountSats: number,
memo: string,
): Promise<LNbitsInvoice> {
const baseUrl = getBaseUrl();
const response = await fetch(`${baseUrl}/api/v1/payments`, {
method: "POST",
headers: getHeaders(),
body: JSON.stringify({
out: false,
amount: amountSats,
memo,
}),
});
if (!response.ok) {
const body = await response.text();
throw new Error(`LNbits createInvoice failed (${response.status}): ${body}`);
}
const data = (await response.json()) as {
payment_hash: string;
payment_request: string;
};
return {
paymentHash: data.payment_hash,
paymentRequest: data.payment_request,
};
}
export async function checkInvoicePaid(
paymentHash: string,
): Promise<LNbitsInvoiceStatus> {
const baseUrl = getBaseUrl();
const response = await fetch(`${baseUrl}/api/v1/payments/${paymentHash}`, {
method: "GET",
headers: getHeaders(),
});
if (!response.ok) {
const body = await response.text();
throw new Error(`LNbits checkInvoice failed (${response.status}): ${body}`);
}
const data = (await response.json()) as {
paid: boolean;
details?: { time?: number };
};
return {
paid: data.paid,
paidAt: data.paid && data.details?.time
? new Date(data.details.time * 1000)
: undefined,
};
}

View File

@@ -0,0 +1,8 @@
export const EVAL_FEE_SATS = 10;
export function computeWorkFeeSats(request: string): number {
const len = request.trim().length;
if (len <= 100) return 50;
if (len <= 300) return 100;
return 250;
}