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:
71
artifacts/api-server/src/lib/agent.ts
Normal file
71
artifacts/api-server/src/lib/agent.ts
Normal 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;
|
||||
}
|
||||
87
artifacts/api-server/src/lib/lnbits.ts
Normal file
87
artifacts/api-server/src/lib/lnbits.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
8
artifacts/api-server/src/lib/pricing.ts
Normal file
8
artifacts/api-server/src/lib/pricing.ts
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user