feat: Claude Sonnet 4.6 provider + Stripe billing automation
Browse filesAI:
- packages/ai-sdk: ClaudeProvider implements LLMProvider via tool_use structured outputs + zod-to-json-schema
- Claude registered at priority 120 (TEXT only), supersedes Gemini for feedback/lessons/chat — -40% cost, better French
- ANTHROPIC_API_KEY added to API and worker env validation
Stripe:
- apps/api/src/routes/stripe.ts: POST /v1/stripe/webhook handles checkout.session.completed, subscription.updated/deleted, invoice.payment_failed/succeeded
- Schema: stripeCustomerId + stripeSubscriptionId added to Organization
- tenant.ts: 402 gate for isHardStopped orgs (x-api-key calls always pass)
- ClientsManagementView: SUSPENDU badge on hard-stopped orgs
- STRIPE_SECRET_KEY + STRIPE_WEBHOOK_SECRET added to config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- apps/admin/src/pages/ClientsManagementView.tsx +7 -0
- apps/api/package.json +1 -0
- apps/api/src/app.ts +3 -0
- apps/api/src/config.ts +3 -0
- apps/api/src/middleware/tenant.ts +9 -0
- apps/api/src/routes/stripe.ts +166 -0
- apps/whatsapp-worker/src/config.ts +1 -0
- packages/ai-sdk/package.json +3 -1
- packages/ai-sdk/src/claude-provider.ts +66 -0
- packages/ai-sdk/src/index.ts +9 -0
- packages/database/prisma/migrations/20260513000002_add_stripe_fields/migration.sql +5 -0
- packages/database/prisma/schema.prisma +2 -0
- pnpm-lock.yaml +79 -0
|
@@ -16,6 +16,8 @@ interface Organization {
|
|
| 16 |
phoneNumbers?: { id: string; phoneNumber: string }[];
|
| 17 |
subscriptionPlan?: 'STARTER' | 'GROWTH' | 'SCALE' | 'ENTERPRISE';
|
| 18 |
subscriptionStatus?: string;
|
|
|
|
|
|
|
| 19 |
personalityConfig?: {
|
| 20 |
botName?: string;
|
| 21 |
coreMission?: string;
|
|
@@ -277,6 +279,11 @@ export default function ClientsManagementView() {
|
|
| 277 |
Crédits IA
|
| 278 |
</button>
|
| 279 |
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
<button
|
| 281 |
onClick={() => { setBillingOrg(client); setBillingPlan(client.subscriptionPlan ?? 'STARTER'); }}
|
| 282 |
className="text-sm font-bold text-indigo-600 hover:text-indigo-700 underline underline-offset-4"
|
|
|
|
| 16 |
phoneNumbers?: { id: string; phoneNumber: string }[];
|
| 17 |
subscriptionPlan?: 'STARTER' | 'GROWTH' | 'SCALE' | 'ENTERPRISE';
|
| 18 |
subscriptionStatus?: string;
|
| 19 |
+
isHardStopped?: boolean;
|
| 20 |
+
stripeCustomerId?: string;
|
| 21 |
personalityConfig?: {
|
| 22 |
botName?: string;
|
| 23 |
coreMission?: string;
|
|
|
|
| 279 |
Crédits IA
|
| 280 |
</button>
|
| 281 |
)}
|
| 282 |
+
{client.isHardStopped && (
|
| 283 |
+
<span className="text-[10px] font-bold bg-red-100 text-red-600 px-2 py-0.5 rounded-full">
|
| 284 |
+
SUSPENDU
|
| 285 |
+
</span>
|
| 286 |
+
)}
|
| 287 |
<button
|
| 288 |
onClick={() => { setBillingOrg(client); setBillingPlan(client.subscriptionPlan ?? 'STARTER'); }}
|
| 289 |
className="text-sm font-bold text-indigo-600 hover:text-indigo-700 underline underline-offset-4"
|
|
@@ -43,6 +43,7 @@
|
|
| 43 |
"pino-pretty": "^13.1.3",
|
| 44 |
"pptxgenjs": "^3.12.0",
|
| 45 |
"puppeteer": "^22.0.0",
|
|
|
|
| 46 |
"web-push": "^3.6.7",
|
| 47 |
"xlsx": "^0.18.5",
|
| 48 |
"zod": "^3.25.76"
|
|
|
|
| 43 |
"pino-pretty": "^13.1.3",
|
| 44 |
"pptxgenjs": "^3.12.0",
|
| 45 |
"puppeteer": "^22.0.0",
|
| 46 |
+
"stripe": "^22.1.1",
|
| 47 |
"web-push": "^3.6.7",
|
| 48 |
"xlsx": "^0.18.5",
|
| 49 |
"zod": "^3.25.76"
|
|
@@ -11,6 +11,7 @@ import { adminRoutes } from './routes/admin';
|
|
| 11 |
import { organizationRoutes } from './routes/organizations';
|
| 12 |
import { aiRoutes } from './routes/ai';
|
| 13 |
import { paymentRoutes } from './routes/payments';
|
|
|
|
| 14 |
import { analyticsRoutes } from './routes/analytics';
|
| 15 |
import { billingRoutes } from './routes/billing';
|
| 16 |
import { notificationRoutes } from './routes/notifications';
|
|
@@ -96,6 +97,8 @@ export async function buildApp() {
|
|
| 96 |
server.register(whatsappRoutes, { prefix: '/v1/whatsapp' });
|
| 97 |
server.register(studentRoutes, { prefix: '/v1/student' });
|
| 98 |
server.register(authRoutes, { prefix: '/v1/auth' });
|
|
|
|
|
|
|
| 99 |
|
| 100 |
// Internal routes (worker→API calls, API-key only — no JWT, no tenant injection)
|
| 101 |
server.register(async (scope) => {
|
|
|
|
| 11 |
import { organizationRoutes } from './routes/organizations';
|
| 12 |
import { aiRoutes } from './routes/ai';
|
| 13 |
import { paymentRoutes } from './routes/payments';
|
| 14 |
+
import { stripeRoutes } from './routes/stripe';
|
| 15 |
import { analyticsRoutes } from './routes/analytics';
|
| 16 |
import { billingRoutes } from './routes/billing';
|
| 17 |
import { notificationRoutes } from './routes/notifications';
|
|
|
|
| 97 |
server.register(whatsappRoutes, { prefix: '/v1/whatsapp' });
|
| 98 |
server.register(studentRoutes, { prefix: '/v1/student' });
|
| 99 |
server.register(authRoutes, { prefix: '/v1/auth' });
|
| 100 |
+
// Stripe webhooks: outside auth scope, needs raw body for HMAC verification
|
| 101 |
+
server.register(stripeRoutes, { prefix: '/v1/stripe' });
|
| 102 |
|
| 103 |
// Internal routes (worker→API calls, API-key only — no JWT, no tenant injection)
|
| 104 |
server.register(async (scope) => {
|
|
@@ -12,6 +12,9 @@ const envSchema = z.object({
|
|
| 12 |
WHATSAPP_PHONE_NUMBER_ID: z.string().optional(),
|
| 13 |
OPENAI_API_KEY: z.string().optional(),
|
| 14 |
GOOGLE_AI_API_KEY: z.string().optional(),
|
|
|
|
|
|
|
|
|
|
| 15 |
R2_ACCOUNT_ID: z.string().optional(),
|
| 16 |
R2_ACCESS_KEY_ID: z.string().optional(),
|
| 17 |
R2_SECRET_ACCESS_KEY: z.string().optional(),
|
|
|
|
| 12 |
WHATSAPP_PHONE_NUMBER_ID: z.string().optional(),
|
| 13 |
OPENAI_API_KEY: z.string().optional(),
|
| 14 |
GOOGLE_AI_API_KEY: z.string().optional(),
|
| 15 |
+
ANTHROPIC_API_KEY: z.string().optional(),
|
| 16 |
+
STRIPE_SECRET_KEY: z.string().optional(),
|
| 17 |
+
STRIPE_WEBHOOK_SECRET: z.string().optional(),
|
| 18 |
R2_ACCOUNT_ID: z.string().optional(),
|
| 19 |
R2_ACCESS_KEY_ID: z.string().optional(),
|
| 20 |
R2_SECRET_ACCESS_KEY: z.string().optional(),
|
|
@@ -19,6 +19,15 @@ export async function injectTenantConfig(request: FastifyRequest, reply: Fastify
|
|
| 19 |
return reply.code(404).send({ error: 'Organization not found' });
|
| 20 |
}
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
// Attach decrypted config to request
|
| 23 |
request.tenantConfig = decryptSecrets(organization);
|
| 24 |
} catch (err) {
|
|
|
|
| 19 |
return reply.code(404).send({ error: 'Organization not found' });
|
| 20 |
}
|
| 21 |
|
| 22 |
+
// Reject requests from hard-stopped (suspended) organizations
|
| 23 |
+
if (organization.isHardStopped) {
|
| 24 |
+
// Super-admin API key calls always pass (needed for management)
|
| 25 |
+
const isInternalCall = request.headers['x-api-key'] === process.env.ADMIN_API_KEY;
|
| 26 |
+
if (!isInternalCall) {
|
| 27 |
+
return reply.code(402).send({ error: 'Service suspended — payment required', code: 'ORG_SUSPENDED' });
|
| 28 |
+
}
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
// Attach decrypted config to request
|
| 32 |
request.tenantConfig = decryptSecrets(organization);
|
| 33 |
} catch (err) {
|
|
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { FastifyInstance } from 'fastify';
|
| 2 |
+
import Stripe from 'stripe';
|
| 3 |
+
import { prisma } from '../services/prisma';
|
| 4 |
+
import { logger } from '../logger';
|
| 5 |
+
|
| 6 |
+
const PRICE_TO_PLAN: Record<string, string> = {
|
| 7 |
+
[process.env.STRIPE_PRICE_STARTER ?? 'price_starter']: 'STARTER',
|
| 8 |
+
[process.env.STRIPE_PRICE_GROWTH ?? 'price_growth']: 'GROWTH',
|
| 9 |
+
[process.env.STRIPE_PRICE_SCALE ?? 'price_scale']: 'SCALE',
|
| 10 |
+
[process.env.STRIPE_PRICE_ENTERPRISE ?? 'price_enterprise']: 'ENTERPRISE',
|
| 11 |
+
};
|
| 12 |
+
|
| 13 |
+
const AI_CREDITS_BY_PLAN: Record<string, number> = {
|
| 14 |
+
STARTER: 500, GROWTH: 3000, SCALE: 10000, ENTERPRISE: 999999,
|
| 15 |
+
};
|
| 16 |
+
|
| 17 |
+
export async function stripeRoutes(fastify: FastifyInstance) {
|
| 18 |
+
if (!process.env.STRIPE_SECRET_KEY || !process.env.STRIPE_WEBHOOK_SECRET) {
|
| 19 |
+
logger.warn('[STRIPE] STRIPE_SECRET_KEY or STRIPE_WEBHOOK_SECRET not set — Stripe webhooks disabled');
|
| 20 |
+
return;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
|
| 24 |
+
|
| 25 |
+
// Stripe requires the raw body for HMAC verification.
|
| 26 |
+
fastify.addContentTypeParser('application/json', { parseAs: 'buffer' }, (_req, body, done) => {
|
| 27 |
+
done(null, body);
|
| 28 |
+
});
|
| 29 |
+
|
| 30 |
+
fastify.post('/webhook', async (req, reply) => {
|
| 31 |
+
const sig = req.headers['stripe-signature'] as string | undefined;
|
| 32 |
+
if (!sig) return reply.code(400).send({ error: 'Missing stripe-signature header' });
|
| 33 |
+
|
| 34 |
+
let event: ReturnType<typeof stripe.webhooks.constructEvent>;
|
| 35 |
+
try {
|
| 36 |
+
event = stripe.webhooks.constructEvent(
|
| 37 |
+
req.body as Buffer,
|
| 38 |
+
sig,
|
| 39 |
+
process.env.STRIPE_WEBHOOK_SECRET!,
|
| 40 |
+
);
|
| 41 |
+
} catch (err: any) {
|
| 42 |
+
logger.warn(`[STRIPE] Signature verification failed: ${err.message}`);
|
| 43 |
+
return reply.code(400).send({ error: `Webhook signature error: ${err.message}` });
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
logger.info(`[STRIPE] Event received: ${event.type}`);
|
| 47 |
+
|
| 48 |
+
try {
|
| 49 |
+
const data = event.data.object as any;
|
| 50 |
+
switch (event.type) {
|
| 51 |
+
case 'checkout.session.completed':
|
| 52 |
+
await handleCheckoutCompleted(data);
|
| 53 |
+
break;
|
| 54 |
+
case 'customer.subscription.updated':
|
| 55 |
+
await handleSubscriptionUpdated(data);
|
| 56 |
+
break;
|
| 57 |
+
case 'customer.subscription.deleted':
|
| 58 |
+
await handleSubscriptionDeleted(data);
|
| 59 |
+
break;
|
| 60 |
+
case 'invoice.payment_failed':
|
| 61 |
+
await handlePaymentFailed(data);
|
| 62 |
+
break;
|
| 63 |
+
case 'invoice.payment_succeeded':
|
| 64 |
+
await handlePaymentSucceeded(data);
|
| 65 |
+
break;
|
| 66 |
+
default:
|
| 67 |
+
break;
|
| 68 |
+
}
|
| 69 |
+
} catch (err: any) {
|
| 70 |
+
logger.error({ err, eventType: event.type }, '[STRIPE] Event handler error');
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
return reply.code(200).send({ received: true });
|
| 74 |
+
});
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
async function handleCheckoutCompleted(session: any) {
|
| 78 |
+
const orgId = session.metadata?.organizationId as string | undefined;
|
| 79 |
+
if (!orgId) return;
|
| 80 |
+
|
| 81 |
+
const plan = (session.metadata?.plan as string | undefined) ?? 'STARTER';
|
| 82 |
+
await prisma.organization.update({
|
| 83 |
+
where: { id: orgId },
|
| 84 |
+
data: {
|
| 85 |
+
stripeCustomerId: session.customer ?? undefined,
|
| 86 |
+
stripeSubscriptionId: session.subscription ?? undefined,
|
| 87 |
+
subscriptionPlan: plan as any,
|
| 88 |
+
subscriptionStatus: 'ACTIVE',
|
| 89 |
+
isHardStopped: false,
|
| 90 |
+
aiCreditsLimit: AI_CREDITS_BY_PLAN[plan] ?? 500,
|
| 91 |
+
},
|
| 92 |
+
});
|
| 93 |
+
logger.info(`[STRIPE] Org ${orgId} activated on plan ${plan}`);
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
async function handleSubscriptionUpdated(subscription: any) {
|
| 97 |
+
const org = await prisma.organization.findFirst({
|
| 98 |
+
where: { stripeCustomerId: subscription.customer as string },
|
| 99 |
+
select: { id: true },
|
| 100 |
+
});
|
| 101 |
+
if (!org) return;
|
| 102 |
+
|
| 103 |
+
const priceId = subscription.items?.data?.[0]?.price?.id ?? '';
|
| 104 |
+
const plan = PRICE_TO_PLAN[priceId] ?? 'STARTER';
|
| 105 |
+
const isActive = subscription.status === 'active' || subscription.status === 'trialing';
|
| 106 |
+
|
| 107 |
+
await prisma.organization.update({
|
| 108 |
+
where: { id: org.id },
|
| 109 |
+
data: {
|
| 110 |
+
stripeSubscriptionId: subscription.id,
|
| 111 |
+
subscriptionPlan: plan as any,
|
| 112 |
+
subscriptionStatus: (subscription.status as string).toUpperCase(),
|
| 113 |
+
isHardStopped: !isActive,
|
| 114 |
+
aiCreditsLimit: isActive ? (AI_CREDITS_BY_PLAN[plan] ?? 500) : 0,
|
| 115 |
+
},
|
| 116 |
+
});
|
| 117 |
+
logger.info(`[STRIPE] Org ${org.id} → plan=${plan} status=${subscription.status}`);
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
async function handleSubscriptionDeleted(subscription: any) {
|
| 121 |
+
const org = await prisma.organization.findFirst({
|
| 122 |
+
where: { stripeCustomerId: subscription.customer as string },
|
| 123 |
+
select: { id: true },
|
| 124 |
+
});
|
| 125 |
+
if (!org) return;
|
| 126 |
+
|
| 127 |
+
await prisma.organization.update({
|
| 128 |
+
where: { id: org.id },
|
| 129 |
+
data: { subscriptionStatus: 'CANCELLED', isHardStopped: true, aiCreditsLimit: 0 },
|
| 130 |
+
});
|
| 131 |
+
logger.info(`[STRIPE] Org ${org.id} subscription cancelled → service suspended`);
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
async function handlePaymentFailed(invoice: any) {
|
| 135 |
+
if (!invoice.customer) return;
|
| 136 |
+
const org = await prisma.organization.findFirst({
|
| 137 |
+
where: { stripeCustomerId: invoice.customer as string },
|
| 138 |
+
select: { id: true },
|
| 139 |
+
});
|
| 140 |
+
if (!org) return;
|
| 141 |
+
|
| 142 |
+
await prisma.organization.update({
|
| 143 |
+
where: { id: org.id },
|
| 144 |
+
data: { subscriptionStatus: 'PAST_DUE', isHardStopped: true },
|
| 145 |
+
});
|
| 146 |
+
logger.warn(`[STRIPE] Org ${org.id} payment failed — service suspended`);
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
async function handlePaymentSucceeded(invoice: any) {
|
| 150 |
+
if (!invoice.customer) return;
|
| 151 |
+
const org = await prisma.organization.findFirst({
|
| 152 |
+
where: { stripeCustomerId: invoice.customer as string },
|
| 153 |
+
select: { id: true, subscriptionPlan: true },
|
| 154 |
+
});
|
| 155 |
+
if (!org) return;
|
| 156 |
+
|
| 157 |
+
await prisma.organization.update({
|
| 158 |
+
where: { id: org.id },
|
| 159 |
+
data: {
|
| 160 |
+
subscriptionStatus: 'ACTIVE',
|
| 161 |
+
isHardStopped: false,
|
| 162 |
+
aiCreditsLimit: AI_CREDITS_BY_PLAN[org.subscriptionPlan] ?? 500,
|
| 163 |
+
},
|
| 164 |
+
});
|
| 165 |
+
logger.info(`[STRIPE] Org ${org.id} payment succeeded — service restored`);
|
| 166 |
+
}
|
|
@@ -13,6 +13,7 @@ const envSchema = z.object({
|
|
| 13 |
WHATSAPP_PHONE_NUMBER_ID: z.string().optional(),
|
| 14 |
OPENAI_API_KEY: z.string().optional(),
|
| 15 |
GOOGLE_AI_API_KEY: z.string().optional(),
|
|
|
|
| 16 |
ENCRYPTION_SECRET: z.string().min(32, 'ENCRYPTION_SECRET must be at least 32 characters'),
|
| 17 |
WORKER_CONCURRENCY: z.string().default('5').transform(Number),
|
| 18 |
NODE_ENV: z.enum(['development', 'production', 'test']).default('development')
|
|
|
|
| 13 |
WHATSAPP_PHONE_NUMBER_ID: z.string().optional(),
|
| 14 |
OPENAI_API_KEY: z.string().optional(),
|
| 15 |
GOOGLE_AI_API_KEY: z.string().optional(),
|
| 16 |
+
ANTHROPIC_API_KEY: z.string().optional(),
|
| 17 |
ENCRYPTION_SECRET: z.string().min(32, 'ENCRYPTION_SECRET must be at least 32 characters'),
|
| 18 |
WORKER_CONCURRENCY: z.string().default('5').transform(Number),
|
| 19 |
NODE_ENV: z.enum(['development', 'production', 'test']).default('development')
|
|
@@ -9,12 +9,14 @@
|
|
| 9 |
"dev": "tsc --watch"
|
| 10 |
},
|
| 11 |
"dependencies": {
|
|
|
|
| 12 |
"@google/generative-ai": "^0.24.1",
|
| 13 |
"@repo/database": "workspace:*",
|
| 14 |
"@repo/prompts": "workspace:*",
|
| 15 |
"axios": "^1.13.5",
|
| 16 |
"openai": "^4.0.0",
|
| 17 |
-
"zod": "^3.25.76"
|
|
|
|
| 18 |
},
|
| 19 |
"devDependencies": {
|
| 20 |
"@repo/tsconfig": "workspace:*",
|
|
|
|
| 9 |
"dev": "tsc --watch"
|
| 10 |
},
|
| 11 |
"dependencies": {
|
| 12 |
+
"@anthropic-ai/sdk": "^0.95.2",
|
| 13 |
"@google/generative-ai": "^0.24.1",
|
| 14 |
"@repo/database": "workspace:*",
|
| 15 |
"@repo/prompts": "workspace:*",
|
| 16 |
"axios": "^1.13.5",
|
| 17 |
"openai": "^4.0.0",
|
| 18 |
+
"zod": "^3.25.76",
|
| 19 |
+
"zod-to-json-schema": "^3.25.2"
|
| 20 |
},
|
| 21 |
"devDependencies": {
|
| 22 |
"@repo/tsconfig": "workspace:*",
|
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import Anthropic from '@anthropic-ai/sdk';
|
| 2 |
+
import { zodToJsonSchema } from 'zod-to-json-schema';
|
| 3 |
+
import { z } from 'zod';
|
| 4 |
+
import { LLMProvider, TokenUsage } from './types';
|
| 5 |
+
import { logger } from './logger';
|
| 6 |
+
|
| 7 |
+
export class ClaudeProvider implements LLMProvider {
|
| 8 |
+
private client: Anthropic;
|
| 9 |
+
private model: string;
|
| 10 |
+
|
| 11 |
+
constructor(apiKey: string, model = 'claude-sonnet-4-6') {
|
| 12 |
+
this.client = new Anthropic({ apiKey, timeout: 60_000, maxRetries: 2 });
|
| 13 |
+
this.model = model;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
async generateText(systemPrompt: string, userPrompt: string, temperature = 0.7): Promise<{ text: string; usage: TokenUsage }> {
|
| 17 |
+
const response = await this.client.messages.create({
|
| 18 |
+
model: this.model,
|
| 19 |
+
max_tokens: 4096,
|
| 20 |
+
temperature,
|
| 21 |
+
system: systemPrompt,
|
| 22 |
+
messages: [{ role: 'user', content: userPrompt }],
|
| 23 |
+
});
|
| 24 |
+
|
| 25 |
+
const text = response.content.filter(b => b.type === 'text').map(b => (b as any).text).join('');
|
| 26 |
+
return {
|
| 27 |
+
text,
|
| 28 |
+
usage: { tokensIn: response.usage.input_tokens, tokensOut: response.usage.output_tokens },
|
| 29 |
+
};
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
async generateStructuredData<T>(prompt: string, schema: z.ZodSchema<T>, temperature = 0.7): Promise<{ data: T; usage: TokenUsage }> {
|
| 33 |
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
| 34 |
+
const jsonSchema = (zodToJsonSchema as any)(schema, { target: 'openApi3' }) as Record<string, unknown>;
|
| 35 |
+
|
| 36 |
+
const response = await this.client.messages.create({
|
| 37 |
+
model: this.model,
|
| 38 |
+
max_tokens: 4096,
|
| 39 |
+
temperature,
|
| 40 |
+
tools: [{
|
| 41 |
+
name: 'output_schema',
|
| 42 |
+
description: 'Output the structured result according to the schema',
|
| 43 |
+
input_schema: jsonSchema as any,
|
| 44 |
+
}],
|
| 45 |
+
tool_choice: { type: 'tool', name: 'output_schema' },
|
| 46 |
+
messages: [{ role: 'user', content: prompt }],
|
| 47 |
+
});
|
| 48 |
+
|
| 49 |
+
const toolBlock = response.content.find(b => b.type === 'tool_use');
|
| 50 |
+
if (!toolBlock || toolBlock.type !== 'tool_use') {
|
| 51 |
+
throw new Error('[CLAUDE] No tool_use block in response');
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
const data = schema.parse(toolBlock.input);
|
| 55 |
+
logger.info(`[CLAUDE] Structured data generated — in:${response.usage.input_tokens} out:${response.usage.output_tokens}`);
|
| 56 |
+
return {
|
| 57 |
+
data,
|
| 58 |
+
usage: { tokensIn: response.usage.input_tokens, tokensOut: response.usage.output_tokens },
|
| 59 |
+
};
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
// Not implemented — Claude not registered for these capabilities
|
| 63 |
+
async transcribeAudio(): Promise<never> { throw new Error('Claude does not support audio transcription'); }
|
| 64 |
+
async generateSpeech(): Promise<never> { throw new Error('Claude does not support speech generation'); }
|
| 65 |
+
async generateImage(): Promise<never> { throw new Error('Claude does not support image generation'); }
|
| 66 |
+
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
| 9 |
} from './types';
|
| 10 |
import { MockLLMProvider } from './mock-provider';
|
| 11 |
import { OpenAIProvider } from './openai-provider';
|
|
|
|
| 12 |
import { searchService } from './search';
|
| 13 |
import { GeminiProvider } from './gemini-provider';
|
| 14 |
import { PromptLoader, PersonalityConfig } from '@repo/prompts';
|
|
@@ -39,9 +40,17 @@ export class AIService {
|
|
| 39 |
}
|
| 40 |
|
| 41 |
private initializeProviders() {
|
|
|
|
| 42 |
const geminiApiKey = process.env.GOOGLE_AI_API_KEY;
|
| 43 |
const openAiApiKey = process.env.OPENAI_API_KEY;
|
| 44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
if (geminiApiKey) {
|
| 46 |
this.registry.register('GEMINI', new GeminiProvider(geminiApiKey), 100, [
|
| 47 |
ProviderCapability.TEXT,
|
|
|
|
| 9 |
} from './types';
|
| 10 |
import { MockLLMProvider } from './mock-provider';
|
| 11 |
import { OpenAIProvider } from './openai-provider';
|
| 12 |
+
import { ClaudeProvider } from './claude-provider';
|
| 13 |
import { searchService } from './search';
|
| 14 |
import { GeminiProvider } from './gemini-provider';
|
| 15 |
import { PromptLoader, PersonalityConfig } from '@repo/prompts';
|
|
|
|
| 40 |
}
|
| 41 |
|
| 42 |
private initializeProviders() {
|
| 43 |
+
const claudeApiKey = process.env.ANTHROPIC_API_KEY;
|
| 44 |
const geminiApiKey = process.env.GOOGLE_AI_API_KEY;
|
| 45 |
const openAiApiKey = process.env.OPENAI_API_KEY;
|
| 46 |
|
| 47 |
+
// Claude: priority 120 for TEXT only (feedback, lessons, chat)
|
| 48 |
+
if (claudeApiKey) {
|
| 49 |
+
this.registry.register('CLAUDE', new ClaudeProvider(claudeApiKey), 120, [
|
| 50 |
+
ProviderCapability.TEXT,
|
| 51 |
+
]);
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
if (geminiApiKey) {
|
| 55 |
this.registry.register('GEMINI', new GeminiProvider(geminiApiKey), 100, [
|
| 56 |
ProviderCapability.TEXT,
|
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
ALTER TABLE "Organization" ADD COLUMN IF NOT EXISTS "stripeCustomerId" TEXT;
|
| 2 |
+
ALTER TABLE "Organization" ADD COLUMN IF NOT EXISTS "stripeSubscriptionId" TEXT;
|
| 3 |
+
|
| 4 |
+
CREATE UNIQUE INDEX IF NOT EXISTS "Organization_stripeCustomerId_key" ON "Organization"("stripeCustomerId");
|
| 5 |
+
CREATE UNIQUE INDEX IF NOT EXISTS "Organization_stripeSubscriptionId_key" ON "Organization"("stripeSubscriptionId");
|
|
@@ -32,6 +32,8 @@ model Organization {
|
|
| 32 |
googleAiApiKey String?
|
| 33 |
subscriptionStatus String? @default("ACTIVE")
|
| 34 |
subscriptionPlan SubscriptionPlan @default(STARTER)
|
|
|
|
|
|
|
| 35 |
aiCreditsLimit Int @default(500)
|
| 36 |
aiCreditsUsed Int @default(0)
|
| 37 |
whatsappMessagesSent Int @default(0)
|
|
|
|
| 32 |
googleAiApiKey String?
|
| 33 |
subscriptionStatus String? @default("ACTIVE")
|
| 34 |
subscriptionPlan SubscriptionPlan @default(STARTER)
|
| 35 |
+
stripeCustomerId String? @unique
|
| 36 |
+
stripeSubscriptionId String? @unique
|
| 37 |
aiCreditsLimit Int @default(500)
|
| 38 |
aiCreditsUsed Int @default(0)
|
| 39 |
whatsappMessagesSent Int @default(0)
|
|
@@ -198,6 +198,9 @@ importers:
|
|
| 198 |
puppeteer:
|
| 199 |
specifier: ^22.0.0
|
| 200 |
version: 22.15.0(typescript@5.9.3)
|
|
|
|
|
|
|
|
|
|
| 201 |
web-push:
|
| 202 |
specifier: ^3.6.7
|
| 203 |
version: 3.6.7
|
|
@@ -375,6 +378,9 @@ importers:
|
|
| 375 |
|
| 376 |
packages/ai-sdk:
|
| 377 |
dependencies:
|
|
|
|
|
|
|
|
|
|
| 378 |
'@google/generative-ai':
|
| 379 |
specifier: ^0.24.1
|
| 380 |
version: 0.24.1
|
|
@@ -393,6 +399,9 @@ importers:
|
|
| 393 |
zod:
|
| 394 |
specifier: ^3.25.76
|
| 395 |
version: 3.25.76
|
|
|
|
|
|
|
|
|
|
| 396 |
devDependencies:
|
| 397 |
'@repo/tsconfig':
|
| 398 |
specifier: workspace:*
|
|
@@ -475,6 +484,15 @@ packages:
|
|
| 475 |
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
| 476 |
engines: {node: '>=10'}
|
| 477 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 478 |
'@aws-crypto/crc32@5.2.0':
|
| 479 |
resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==}
|
| 480 |
engines: {node: '>=16.0.0'}
|
|
@@ -2217,6 +2235,9 @@ packages:
|
|
| 2217 |
resolution: {integrity: sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==}
|
| 2218 |
engines: {node: '>=18.0.0'}
|
| 2219 |
|
|
|
|
|
|
|
|
|
|
| 2220 |
'@standard-schema/spec@1.1.0':
|
| 2221 |
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
| 2222 |
|
|
@@ -3189,6 +3210,9 @@ packages:
|
|
| 3189 |
fast-safe-stringify@2.1.1:
|
| 3190 |
resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
|
| 3191 |
|
|
|
|
|
|
|
|
|
|
| 3192 |
fast-uri@2.4.0:
|
| 3193 |
resolution: {integrity: sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA==}
|
| 3194 |
|
|
@@ -3581,6 +3605,10 @@ packages:
|
|
| 3581 |
json-schema-ref-resolver@1.0.1:
|
| 3582 |
resolution: {integrity: sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==}
|
| 3583 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3584 |
json-schema-traverse@1.0.0:
|
| 3585 |
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
|
| 3586 |
|
|
@@ -4470,6 +4498,9 @@ packages:
|
|
| 4470 |
standard-as-callback@2.1.0:
|
| 4471 |
resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==}
|
| 4472 |
|
|
|
|
|
|
|
|
|
|
| 4473 |
statuses@2.0.1:
|
| 4474 |
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
| 4475 |
engines: {node: '>= 0.8'}
|
|
@@ -4520,6 +4551,15 @@ packages:
|
|
| 4520 |
strip-literal@2.1.1:
|
| 4521 |
resolution: {integrity: sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==}
|
| 4522 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4523 |
strnum@2.1.2:
|
| 4524 |
resolution: {integrity: sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==}
|
| 4525 |
|
|
@@ -4621,6 +4661,9 @@ packages:
|
|
| 4621 |
tr46@0.0.3:
|
| 4622 |
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
| 4623 |
|
|
|
|
|
|
|
|
|
|
| 4624 |
ts-interface-checker@0.1.13:
|
| 4625 |
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
|
| 4626 |
|
|
@@ -5006,6 +5049,11 @@ packages:
|
|
| 5006 |
resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==}
|
| 5007 |
engines: {node: '>=12.20'}
|
| 5008 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5009 |
zod@3.23.8:
|
| 5010 |
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
|
| 5011 |
|
|
@@ -5019,6 +5067,13 @@ snapshots:
|
|
| 5019 |
|
| 5020 |
'@alloc/quick-lru@5.2.0': {}
|
| 5021 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5022 |
'@aws-crypto/crc32@5.2.0':
|
| 5023 |
dependencies:
|
| 5024 |
'@aws-crypto/util': 5.2.0
|
|
@@ -7073,6 +7128,8 @@ snapshots:
|
|
| 7073 |
dependencies:
|
| 7074 |
tslib: 2.8.1
|
| 7075 |
|
|
|
|
|
|
|
| 7076 |
'@standard-schema/spec@1.1.0': {}
|
| 7077 |
|
| 7078 |
'@standard-schema/utils@0.3.0': {}
|
|
@@ -8093,6 +8150,8 @@ snapshots:
|
|
| 8093 |
|
| 8094 |
fast-safe-stringify@2.1.1: {}
|
| 8095 |
|
|
|
|
|
|
|
| 8096 |
fast-uri@2.4.0: {}
|
| 8097 |
|
| 8098 |
fast-uri@3.1.0: {}
|
|
@@ -8487,6 +8546,11 @@ snapshots:
|
|
| 8487 |
dependencies:
|
| 8488 |
fast-deep-equal: 3.1.3
|
| 8489 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8490 |
json-schema-traverse@1.0.0: {}
|
| 8491 |
|
| 8492 |
json-stringify-safe@5.0.1:
|
|
@@ -9500,6 +9564,11 @@ snapshots:
|
|
| 9500 |
|
| 9501 |
standard-as-callback@2.1.0: {}
|
| 9502 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9503 |
statuses@2.0.1: {}
|
| 9504 |
|
| 9505 |
std-env@3.10.0: {}
|
|
@@ -9559,6 +9628,10 @@ snapshots:
|
|
| 9559 |
dependencies:
|
| 9560 |
js-tokens: 9.0.1
|
| 9561 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9562 |
strnum@2.1.2: {}
|
| 9563 |
|
| 9564 |
sucrase@3.35.1:
|
|
@@ -9690,6 +9763,8 @@ snapshots:
|
|
| 9690 |
|
| 9691 |
tr46@0.0.3: {}
|
| 9692 |
|
|
|
|
|
|
|
| 9693 |
ts-interface-checker@0.1.13: {}
|
| 9694 |
|
| 9695 |
ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3):
|
|
@@ -10041,6 +10116,10 @@ snapshots:
|
|
| 10041 |
|
| 10042 |
yocto-queue@1.2.2: {}
|
| 10043 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10044 |
zod@3.23.8: {}
|
| 10045 |
|
| 10046 |
zod@3.25.76: {}
|
|
|
|
| 198 |
puppeteer:
|
| 199 |
specifier: ^22.0.0
|
| 200 |
version: 22.15.0(typescript@5.9.3)
|
| 201 |
+
stripe:
|
| 202 |
+
specifier: ^22.1.1
|
| 203 |
+
version: 22.1.1(@types/node@20.19.33)
|
| 204 |
web-push:
|
| 205 |
specifier: ^3.6.7
|
| 206 |
version: 3.6.7
|
|
|
|
| 378 |
|
| 379 |
packages/ai-sdk:
|
| 380 |
dependencies:
|
| 381 |
+
'@anthropic-ai/sdk':
|
| 382 |
+
specifier: ^0.95.2
|
| 383 |
+
version: 0.95.2(zod@3.25.76)
|
| 384 |
'@google/generative-ai':
|
| 385 |
specifier: ^0.24.1
|
| 386 |
version: 0.24.1
|
|
|
|
| 399 |
zod:
|
| 400 |
specifier: ^3.25.76
|
| 401 |
version: 3.25.76
|
| 402 |
+
zod-to-json-schema:
|
| 403 |
+
specifier: ^3.25.2
|
| 404 |
+
version: 3.25.2(zod@3.25.76)
|
| 405 |
devDependencies:
|
| 406 |
'@repo/tsconfig':
|
| 407 |
specifier: workspace:*
|
|
|
|
| 484 |
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
| 485 |
engines: {node: '>=10'}
|
| 486 |
|
| 487 |
+
'@anthropic-ai/sdk@0.95.2':
|
| 488 |
+
resolution: {integrity: sha512-Egddwo3sheo1PzUrMkZnH6VkQYwS0h/b/i8vSK8Ta9M45UQipAMeDFH57dYuDAfXMEUUGeKw6CMlremgMZgrSQ==}
|
| 489 |
+
hasBin: true
|
| 490 |
+
peerDependencies:
|
| 491 |
+
zod: ^3.25.0 || ^4.0.0
|
| 492 |
+
peerDependenciesMeta:
|
| 493 |
+
zod:
|
| 494 |
+
optional: true
|
| 495 |
+
|
| 496 |
'@aws-crypto/crc32@5.2.0':
|
| 497 |
resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==}
|
| 498 |
engines: {node: '>=16.0.0'}
|
|
|
|
| 2235 |
resolution: {integrity: sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==}
|
| 2236 |
engines: {node: '>=18.0.0'}
|
| 2237 |
|
| 2238 |
+
'@stablelib/base64@1.0.1':
|
| 2239 |
+
resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==}
|
| 2240 |
+
|
| 2241 |
'@standard-schema/spec@1.1.0':
|
| 2242 |
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
| 2243 |
|
|
|
|
| 3210 |
fast-safe-stringify@2.1.1:
|
| 3211 |
resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
|
| 3212 |
|
| 3213 |
+
fast-sha256@1.3.0:
|
| 3214 |
+
resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==}
|
| 3215 |
+
|
| 3216 |
fast-uri@2.4.0:
|
| 3217 |
resolution: {integrity: sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA==}
|
| 3218 |
|
|
|
|
| 3605 |
json-schema-ref-resolver@1.0.1:
|
| 3606 |
resolution: {integrity: sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==}
|
| 3607 |
|
| 3608 |
+
json-schema-to-ts@3.1.1:
|
| 3609 |
+
resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==}
|
| 3610 |
+
engines: {node: '>=16'}
|
| 3611 |
+
|
| 3612 |
json-schema-traverse@1.0.0:
|
| 3613 |
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
|
| 3614 |
|
|
|
|
| 4498 |
standard-as-callback@2.1.0:
|
| 4499 |
resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==}
|
| 4500 |
|
| 4501 |
+
standardwebhooks@1.0.0:
|
| 4502 |
+
resolution: {integrity: sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==}
|
| 4503 |
+
|
| 4504 |
statuses@2.0.1:
|
| 4505 |
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
| 4506 |
engines: {node: '>= 0.8'}
|
|
|
|
| 4551 |
strip-literal@2.1.1:
|
| 4552 |
resolution: {integrity: sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==}
|
| 4553 |
|
| 4554 |
+
stripe@22.1.1:
|
| 4555 |
+
resolution: {integrity: sha512-cmodIYP27tBkJ8G7DuGgWw0PFuemlFZbuF3Wwr1TrjFjUa3T7NIgCe6TVwX8BO2ynu+xtTuDGfHafNDCPt9lXA==}
|
| 4556 |
+
engines: {node: '>=18'}
|
| 4557 |
+
peerDependencies:
|
| 4558 |
+
'@types/node': '>=18'
|
| 4559 |
+
peerDependenciesMeta:
|
| 4560 |
+
'@types/node':
|
| 4561 |
+
optional: true
|
| 4562 |
+
|
| 4563 |
strnum@2.1.2:
|
| 4564 |
resolution: {integrity: sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==}
|
| 4565 |
|
|
|
|
| 4661 |
tr46@0.0.3:
|
| 4662 |
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
| 4663 |
|
| 4664 |
+
ts-algebra@2.0.0:
|
| 4665 |
+
resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==}
|
| 4666 |
+
|
| 4667 |
ts-interface-checker@0.1.13:
|
| 4668 |
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
|
| 4669 |
|
|
|
|
| 5049 |
resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==}
|
| 5050 |
engines: {node: '>=12.20'}
|
| 5051 |
|
| 5052 |
+
zod-to-json-schema@3.25.2:
|
| 5053 |
+
resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==}
|
| 5054 |
+
peerDependencies:
|
| 5055 |
+
zod: ^3.25.28 || ^4
|
| 5056 |
+
|
| 5057 |
zod@3.23.8:
|
| 5058 |
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
|
| 5059 |
|
|
|
|
| 5067 |
|
| 5068 |
'@alloc/quick-lru@5.2.0': {}
|
| 5069 |
|
| 5070 |
+
'@anthropic-ai/sdk@0.95.2(zod@3.25.76)':
|
| 5071 |
+
dependencies:
|
| 5072 |
+
json-schema-to-ts: 3.1.1
|
| 5073 |
+
standardwebhooks: 1.0.0
|
| 5074 |
+
optionalDependencies:
|
| 5075 |
+
zod: 3.25.76
|
| 5076 |
+
|
| 5077 |
'@aws-crypto/crc32@5.2.0':
|
| 5078 |
dependencies:
|
| 5079 |
'@aws-crypto/util': 5.2.0
|
|
|
|
| 7128 |
dependencies:
|
| 7129 |
tslib: 2.8.1
|
| 7130 |
|
| 7131 |
+
'@stablelib/base64@1.0.1': {}
|
| 7132 |
+
|
| 7133 |
'@standard-schema/spec@1.1.0': {}
|
| 7134 |
|
| 7135 |
'@standard-schema/utils@0.3.0': {}
|
|
|
|
| 8150 |
|
| 8151 |
fast-safe-stringify@2.1.1: {}
|
| 8152 |
|
| 8153 |
+
fast-sha256@1.3.0: {}
|
| 8154 |
+
|
| 8155 |
fast-uri@2.4.0: {}
|
| 8156 |
|
| 8157 |
fast-uri@3.1.0: {}
|
|
|
|
| 8546 |
dependencies:
|
| 8547 |
fast-deep-equal: 3.1.3
|
| 8548 |
|
| 8549 |
+
json-schema-to-ts@3.1.1:
|
| 8550 |
+
dependencies:
|
| 8551 |
+
'@babel/runtime': 7.29.2
|
| 8552 |
+
ts-algebra: 2.0.0
|
| 8553 |
+
|
| 8554 |
json-schema-traverse@1.0.0: {}
|
| 8555 |
|
| 8556 |
json-stringify-safe@5.0.1:
|
|
|
|
| 9564 |
|
| 9565 |
standard-as-callback@2.1.0: {}
|
| 9566 |
|
| 9567 |
+
standardwebhooks@1.0.0:
|
| 9568 |
+
dependencies:
|
| 9569 |
+
'@stablelib/base64': 1.0.1
|
| 9570 |
+
fast-sha256: 1.3.0
|
| 9571 |
+
|
| 9572 |
statuses@2.0.1: {}
|
| 9573 |
|
| 9574 |
std-env@3.10.0: {}
|
|
|
|
| 9628 |
dependencies:
|
| 9629 |
js-tokens: 9.0.1
|
| 9630 |
|
| 9631 |
+
stripe@22.1.1(@types/node@20.19.33):
|
| 9632 |
+
optionalDependencies:
|
| 9633 |
+
'@types/node': 20.19.33
|
| 9634 |
+
|
| 9635 |
strnum@2.1.2: {}
|
| 9636 |
|
| 9637 |
sucrase@3.35.1:
|
|
|
|
| 9763 |
|
| 9764 |
tr46@0.0.3: {}
|
| 9765 |
|
| 9766 |
+
ts-algebra@2.0.0: {}
|
| 9767 |
+
|
| 9768 |
ts-interface-checker@0.1.13: {}
|
| 9769 |
|
| 9770 |
ts-node@10.9.2(@types/node@20.19.33)(typescript@5.9.3):
|
|
|
|
| 10116 |
|
| 10117 |
yocto-queue@1.2.2: {}
|
| 10118 |
|
| 10119 |
+
zod-to-json-schema@3.25.2(zod@3.25.76):
|
| 10120 |
+
dependencies:
|
| 10121 |
+
zod: 3.25.76
|
| 10122 |
+
|
| 10123 |
zod@3.23.8: {}
|
| 10124 |
|
| 10125 |
zod@3.25.76: {}
|