@agentforge-io/core 2.0.24 → 2.1.0

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.
Files changed (88) hide show
  1. package/dist/factory.js +56 -1
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.js +7 -1
  4. package/dist/services/agent-runner.service.js +18 -4
  5. package/dist/services/agent.service.d.ts +21 -1
  6. package/dist/services/agent.service.js +42 -8
  7. package/dist/services/orchestrator.service.d.ts +40 -1
  8. package/dist/services/orchestrator.service.js +220 -0
  9. package/dist/types/agent.types.d.ts +31 -6
  10. package/package.json +1 -1
  11. package/dist/adapters/billing/billing-adapter.interface.d.ts +0 -41
  12. package/dist/adapters/billing/billing-adapter.interface.js +0 -5
  13. package/dist/adapters/billing/stripe/stripe.adapter.d.ts +0 -30
  14. package/dist/adapters/billing/stripe/stripe.adapter.js +0 -122
  15. package/dist/adapters/email/email-adapter.interface.d.ts +0 -25
  16. package/dist/adapters/email/email-adapter.interface.js +0 -6
  17. package/dist/adapters/email/noop.adapter.d.ts +0 -10
  18. package/dist/adapters/email/noop.adapter.js +0 -15
  19. package/dist/adapters/email/resend.adapter.d.ts +0 -8
  20. package/dist/adapters/email/resend.adapter.js +0 -39
  21. package/dist/adapters/upload/noop.adapter.d.ts +0 -9
  22. package/dist/adapters/upload/noop.adapter.js +0 -14
  23. package/dist/adapters/upload/s3.adapter.d.ts +0 -38
  24. package/dist/adapters/upload/s3.adapter.js +0 -69
  25. package/dist/adapters/upload/upload-adapter.interface.d.ts +0 -37
  26. package/dist/adapters/upload/upload-adapter.interface.js +0 -15
  27. package/dist/billing/index.d.ts +0 -12
  28. package/dist/billing/index.js +0 -28
  29. package/dist/domain/agent.d.ts +0 -59
  30. package/dist/domain/agent.js +0 -2
  31. package/dist/domain/api-key.d.ts +0 -28
  32. package/dist/domain/api-key.js +0 -2
  33. package/dist/domain/auth-identity.d.ts +0 -10
  34. package/dist/domain/auth-identity.js +0 -2
  35. package/dist/domain/email-token.d.ts +0 -11
  36. package/dist/domain/email-token.js +0 -2
  37. package/dist/domain/external-user.d.ts +0 -23
  38. package/dist/domain/external-user.js +0 -2
  39. package/dist/domain/plan.d.ts +0 -20
  40. package/dist/domain/plan.js +0 -2
  41. package/dist/domain/platform-secret.d.ts +0 -24
  42. package/dist/domain/platform-secret.js +0 -8
  43. package/dist/domain/refresh-token.d.ts +0 -15
  44. package/dist/domain/refresh-token.js +0 -2
  45. package/dist/domain/subscription.d.ts +0 -21
  46. package/dist/domain/subscription.js +0 -2
  47. package/dist/domain/tenant.d.ts +0 -21
  48. package/dist/domain/tenant.js +0 -2
  49. package/dist/domain/usage-record.d.ts +0 -15
  50. package/dist/domain/usage-record.js +0 -2
  51. package/dist/domain/user.d.ts +0 -43
  52. package/dist/domain/user.js +0 -2
  53. package/dist/services/agent-config.service.d.ts +0 -45
  54. package/dist/services/agent-config.service.js +0 -114
  55. package/dist/services/api-key.service.d.ts +0 -41
  56. package/dist/services/api-key.service.js +0 -80
  57. package/dist/services/auth.service.d.ts +0 -133
  58. package/dist/services/auth.service.js +0 -411
  59. package/dist/services/billing.service.d.ts +0 -67
  60. package/dist/services/billing.service.js +0 -254
  61. package/dist/services/email-templates.d.ts +0 -18
  62. package/dist/services/email-templates.js +0 -39
  63. package/dist/services/email.service.d.ts +0 -26
  64. package/dist/services/email.service.js +0 -42
  65. package/dist/services/errors.d.ts +0 -7
  66. package/dist/services/errors.js +0 -27
  67. package/dist/services/oauth.service.d.ts +0 -73
  68. package/dist/services/oauth.service.js +0 -174
  69. package/dist/services/plan.service.d.ts +0 -54
  70. package/dist/services/plan.service.js +0 -120
  71. package/dist/services/refresh-token.service.d.ts +0 -38
  72. package/dist/services/refresh-token.service.js +0 -73
  73. package/dist/services/secrets/crypto.d.ts +0 -37
  74. package/dist/services/secrets/crypto.js +0 -110
  75. package/dist/services/secrets/known-keys.d.ts +0 -38
  76. package/dist/services/secrets/known-keys.js +0 -50
  77. package/dist/services/secrets.service.d.ts +0 -91
  78. package/dist/services/secrets.service.js +0 -193
  79. package/dist/services/tenant-billing.service.d.ts +0 -121
  80. package/dist/services/tenant-billing.service.js +0 -290
  81. package/dist/services/tenant.service.d.ts +0 -54
  82. package/dist/services/tenant.service.js +0 -96
  83. package/dist/services/upload.service.d.ts +0 -37
  84. package/dist/services/upload.service.js +0 -84
  85. package/dist/services/usage.service.d.ts +0 -34
  86. package/dist/services/usage.service.js +0 -108
  87. package/dist/types/billing.types.d.ts +0 -82
  88. package/dist/types/billing.types.js +0 -3
@@ -1,96 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TenantService = exports.TenantError = void 0;
4
- const crypto_1 = require("crypto");
5
- class TenantError extends Error {
6
- constructor(code, message) {
7
- super(message);
8
- this.code = code;
9
- this.status = code === 'forbidden' ? 403 : 404;
10
- }
11
- }
12
- exports.TenantError = TenantError;
13
- class TenantService {
14
- constructor(tenants, externalUsers, users) {
15
- this.tenants = tenants;
16
- this.externalUsers = externalUsers;
17
- this.users = users;
18
- }
19
- // ─── Tenant CRUD (admin) ─────────────────────────────────────────────────
20
- async create(input) {
21
- const tenant = await this.tenants.create({
22
- id: (0, crypto_1.randomUUID)(),
23
- name: input.name,
24
- ownerUserId: input.ownerUserId,
25
- planId: input.planId ?? 'free',
26
- isActive: true,
27
- });
28
- return tenant;
29
- }
30
- async findById(id) {
31
- return this.tenants.findById(id);
32
- }
33
- async listForOwner(ownerUserId, opts = {}) {
34
- return this.tenants.listForOwner(ownerUserId, opts);
35
- }
36
- /**
37
- * List every tenant in the system. Controllers MUST gate this behind a
38
- * platform-admin check before calling — the service intentionally doesn't,
39
- * so it stays usable from contexts that have already authorized the call.
40
- */
41
- async listAll(opts = {}) {
42
- return this.tenants.listAll(opts);
43
- }
44
- /**
45
- * Throws if the user isn't the owner. Used by admin endpoints.
46
- *
47
- * Pass `allowAnyOwner: true` to skip the owner check — controllers should
48
- * set this when the caller is a platform admin, so super-admins can manage
49
- * any tenant. The not-found path still fires when the tenantId is bogus.
50
- */
51
- async assertOwner(tenantId, userId, opts = {}) {
52
- const tenant = await this.tenants.findById(tenantId);
53
- if (!tenant)
54
- throw new TenantError('not_found', 'Tenant not found');
55
- if (!opts.allowAnyOwner && tenant.ownerUserId !== userId) {
56
- // Don't reveal existence — return not_found rather than forbidden.
57
- throw new TenantError('not_found', 'Tenant not found');
58
- }
59
- return tenant;
60
- }
61
- // ─── External user mapping (B2B request path) ────────────────────────────
62
- /**
63
- * Look up (tenantId, externalId) → internal User. Creates the linkage on
64
- * first use. Also creates a placeholder User row so the rest of the system
65
- * (conversations, usage, billing) can reference it.
66
- */
67
- async ensureExternalUser(input) {
68
- const existing = await this.externalUsers.findByExternalId(input.tenantId, input.externalId);
69
- if (existing)
70
- return existing;
71
- const internalId = (0, crypto_1.randomUUID)();
72
- // Create a User row so conversations/messages/usage can FK to it. The
73
- // user is "owned" by the tenant — we store metadata for traceability.
74
- await this.users.create({
75
- id: internalId,
76
- email: input.email,
77
- name: input.name,
78
- authProvider: `tenant:${input.tenantId}`,
79
- currentPlanId: 'free',
80
- creditsBalance: 0,
81
- isActive: true,
82
- failedLoginCount: 0,
83
- metadata: {
84
- tenantId: input.tenantId,
85
- externalId: input.externalId,
86
- },
87
- });
88
- return this.externalUsers.create({
89
- id: internalId,
90
- tenantId: input.tenantId,
91
- externalId: input.externalId,
92
- email: input.email,
93
- });
94
- }
95
- }
96
- exports.TenantService = TenantService;
@@ -1,37 +0,0 @@
1
- import type { SignedUploadTarget, UploadAdapter } from '../adapters/upload/upload-adapter.interface';
2
- export declare class UploadError extends Error {
3
- status: number;
4
- code: 'unsupported_type' | 'too_large' | 'invalid' | 'not_configured';
5
- constructor(code: UploadError['code'], message: string);
6
- }
7
- export interface AvatarSignInput {
8
- tenantId: string;
9
- agentId: string;
10
- contentType: string;
11
- /** Declared size in bytes (best-effort — client computes from File.size).
12
- * We reject before signing if it exceeds the limit. */
13
- contentLength?: number;
14
- }
15
- /**
16
- * Thin application service over the UploadAdapter. Owns:
17
- * - MIME / size validation (so we don't sign URLs for forbidden uploads)
18
- * - Key construction (keeps adapter ignorant of domain conventions)
19
- * - Error shaping for the HTTP layer
20
- *
21
- * Adapter swapping (S3 → R2 → MinIO) doesn't ripple into the controller
22
- * because everything HTTP-flavored happens here.
23
- */
24
- export declare class UploadService {
25
- private readonly adapter;
26
- constructor(adapter: UploadAdapter);
27
- /**
28
- * Sign a PUT URL for an agent's avatar. Key shape:
29
- * agents/<tenantId>/<agentId>/avatar-<uuid>.<ext>
30
- * The UUID makes a fresh upload not collide with the previous avatar —
31
- * we keep the old object around (cleanup is a future improvement) so we
32
- * don't 404 anyone who already had the old URL loaded.
33
- */
34
- signAgentAvatar(input: AvatarSignInput): Promise<SignedUploadTarget>;
35
- }
36
- export declare const ALLOWED_AVATAR_MIME_TYPES: string[];
37
- export declare const MAX_AVATAR_BYTES: number;
@@ -1,84 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MAX_AVATAR_BYTES = exports.ALLOWED_AVATAR_MIME_TYPES = exports.UploadService = exports.UploadError = void 0;
4
- const crypto_1 = require("crypto");
5
- class UploadError extends Error {
6
- constructor(code, message) {
7
- super(message);
8
- this.code = code;
9
- this.status =
10
- code === 'not_configured' ? 501 :
11
- code === 'too_large' ? 413 :
12
- code === 'unsupported_type' ? 415 : 400;
13
- }
14
- }
15
- exports.UploadError = UploadError;
16
- const IMAGE_MIME_WHITELIST = new Set([
17
- 'image/png',
18
- 'image/jpeg',
19
- 'image/webp',
20
- 'image/gif',
21
- ]);
22
- const MIME_TO_EXT = {
23
- 'image/png': 'png',
24
- 'image/jpeg': 'jpg',
25
- 'image/webp': 'webp',
26
- 'image/gif': 'gif',
27
- };
28
- /** Max image upload size (bytes). Avatars are tiny; this catches an upload
29
- * the user thinks is a 1MB photo but is actually a 30MB original. */
30
- const MAX_IMAGE_BYTES = 2 * 1024 * 1024;
31
- /**
32
- * Thin application service over the UploadAdapter. Owns:
33
- * - MIME / size validation (so we don't sign URLs for forbidden uploads)
34
- * - Key construction (keeps adapter ignorant of domain conventions)
35
- * - Error shaping for the HTTP layer
36
- *
37
- * Adapter swapping (S3 → R2 → MinIO) doesn't ripple into the controller
38
- * because everything HTTP-flavored happens here.
39
- */
40
- class UploadService {
41
- constructor(adapter) {
42
- this.adapter = adapter;
43
- }
44
- /**
45
- * Sign a PUT URL for an agent's avatar. Key shape:
46
- * agents/<tenantId>/<agentId>/avatar-<uuid>.<ext>
47
- * The UUID makes a fresh upload not collide with the previous avatar —
48
- * we keep the old object around (cleanup is a future improvement) so we
49
- * don't 404 anyone who already had the old URL loaded.
50
- */
51
- async signAgentAvatar(input) {
52
- if (!input.tenantId || !input.agentId) {
53
- throw new UploadError('invalid', 'tenantId and agentId are required');
54
- }
55
- const ct = input.contentType?.toLowerCase().trim();
56
- if (!ct || !IMAGE_MIME_WHITELIST.has(ct)) {
57
- throw new UploadError('unsupported_type', `Unsupported image type "${input.contentType}". Allowed: ${[...IMAGE_MIME_WHITELIST].join(', ')}.`);
58
- }
59
- if (input.contentLength !== undefined && input.contentLength > MAX_IMAGE_BYTES) {
60
- throw new UploadError('too_large', `Avatar must be ${(MAX_IMAGE_BYTES / 1024 / 1024).toFixed(1)} MB or smaller.`);
61
- }
62
- const ext = MIME_TO_EXT[ct] ?? 'bin';
63
- const key = `agents/${input.tenantId}/${input.agentId}/avatar-${(0, crypto_1.randomUUID)()}.${ext}`;
64
- try {
65
- return await this.adapter.signUpload({
66
- key,
67
- contentType: ct,
68
- maxSizeBytes: MAX_IMAGE_BYTES,
69
- });
70
- }
71
- catch (err) {
72
- // NoopUploadAdapter throws this exact message — map it to a sensible
73
- // 501 instead of leaking the adapter shape to the client.
74
- const msg = err instanceof Error ? err.message : String(err);
75
- if (msg.includes('Uploads are not configured')) {
76
- throw new UploadError('not_configured', msg);
77
- }
78
- throw err;
79
- }
80
- }
81
- }
82
- exports.UploadService = UploadService;
83
- exports.ALLOWED_AVATAR_MIME_TYPES = [...IMAGE_MIME_WHITELIST];
84
- exports.MAX_AVATAR_BYTES = MAX_IMAGE_BYTES;
@@ -1,34 +0,0 @@
1
- import type { UserRepository, UsageRecordRepository } from '../repositories';
2
- import type { CreditsConfig, UsageLimits } from '../types/config.types';
3
- import type { PlanService } from './plan.service';
4
- import type { UsageSummary } from '../types/billing.types';
5
- import type { TokenUsage } from '../types/agent.types';
6
- export interface UsageServiceOptions {
7
- plans: PlanService;
8
- defaultLimits?: UsageLimits;
9
- credits?: CreditsConfig;
10
- }
11
- export declare class UsageService {
12
- private readonly users;
13
- private readonly usage;
14
- private readonly opts;
15
- constructor(users: UserRepository, usage: UsageRecordRepository, opts: UsageServiceOptions);
16
- record(params: {
17
- userId: string;
18
- conversationId: string;
19
- messageId: string;
20
- agentId: string;
21
- usage: TokenUsage;
22
- }): Promise<void>;
23
- checkLimits(userId: string): Promise<{
24
- allowed: boolean;
25
- reason?: string;
26
- usage: UsageSummary;
27
- }>;
28
- getSummary(userId: string): Promise<UsageSummary>;
29
- /** Idempotent: create a user row if missing. Used by the agent flow. */
30
- ensureUser(userId: string): Promise<void>;
31
- private currentBillingPeriod;
32
- private periodDates;
33
- private getUserPlan;
34
- }
@@ -1,108 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.UsageService = void 0;
4
- class UsageService {
5
- constructor(users, usage, opts) {
6
- this.users = users;
7
- this.usage = usage;
8
- this.opts = opts;
9
- }
10
- async record(params) {
11
- const period = this.currentBillingPeriod();
12
- const credits = this.opts.credits;
13
- const creditsConsumed = credits
14
- ? params.usage.totalTokens / credits.tokensPerCredit + (credits.creditsPerRequest ?? 0)
15
- : undefined;
16
- await this.usage.create({
17
- userId: params.userId,
18
- conversationId: params.conversationId,
19
- messageId: params.messageId,
20
- agentId: params.agentId,
21
- inputTokens: params.usage.inputTokens,
22
- outputTokens: params.usage.outputTokens,
23
- totalTokens: params.usage.totalTokens,
24
- requestCount: 1,
25
- creditsConsumed,
26
- billingPeriod: period,
27
- });
28
- }
29
- async checkLimits(userId) {
30
- const summary = await this.getSummary(userId);
31
- if (summary.isOverLimit) {
32
- const reasons = [];
33
- const { requestsLimit, tokensLimit } = summary;
34
- if (requestsLimit > 0 && summary.requestsUsed >= requestsLimit) {
35
- reasons.push(`Monthly request limit reached (${requestsLimit} requests)`);
36
- }
37
- if (tokensLimit > 0 && summary.tokensUsed >= tokensLimit) {
38
- reasons.push(`Monthly token limit reached (${tokensLimit} tokens)`);
39
- }
40
- return { allowed: false, reason: reasons.join('; '), usage: summary };
41
- }
42
- if (this.opts.credits) {
43
- const user = await this.users.findById(userId);
44
- if (user && user.creditsBalance <= 0) {
45
- return { allowed: false, reason: 'Insufficient credits', usage: summary };
46
- }
47
- }
48
- return { allowed: true, usage: summary };
49
- }
50
- async getSummary(userId) {
51
- const period = this.currentBillingPeriod();
52
- const plan = await this.getUserPlan(userId);
53
- const limits = plan?.limits ?? this.opts.defaultLimits ?? {};
54
- const summed = await this.usage.sumForPeriod(userId, period);
55
- const requestsUsed = summed.requestCount;
56
- const tokensUsed = summed.totalTokens;
57
- const requestsLimit = limits.requestsPerMonth ?? -1;
58
- const tokensLimit = limits.tokensPerMonth ?? -1;
59
- const [periodStart, periodEnd] = this.periodDates(period);
60
- return {
61
- userId,
62
- planId: plan?.id ?? 'free',
63
- periodStart,
64
- periodEnd,
65
- requestsUsed,
66
- tokensUsed,
67
- requestsLimit,
68
- tokensLimit,
69
- percentUsed: {
70
- requests: requestsLimit > 0 ? (requestsUsed / requestsLimit) * 100 : 0,
71
- tokens: tokensLimit > 0 ? (tokensUsed / tokensLimit) * 100 : 0,
72
- },
73
- isOverLimit: (requestsLimit > 0 && requestsUsed >= requestsLimit) ||
74
- (tokensLimit > 0 && tokensUsed >= tokensLimit),
75
- };
76
- }
77
- /** Idempotent: create a user row if missing. Used by the agent flow. */
78
- async ensureUser(userId) {
79
- const existing = await this.users.findById(userId);
80
- if (existing)
81
- return;
82
- const defaultPlanId = await this.opts.plans.getDefaultId();
83
- await this.users.create({
84
- id: userId,
85
- currentPlanId: defaultPlanId,
86
- creditsBalance: this.opts.credits?.initialCredits ?? 0,
87
- isActive: true,
88
- failedLoginCount: 0,
89
- });
90
- }
91
- currentBillingPeriod() {
92
- const now = new Date();
93
- return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
94
- }
95
- periodDates(period) {
96
- const [year, month] = period.split('-').map(Number);
97
- const start = new Date(year, month - 1, 1);
98
- const end = new Date(year, month, 0, 23, 59, 59);
99
- return [start, end];
100
- }
101
- async getUserPlan(userId) {
102
- const user = await this.users.findById(userId);
103
- if (!user)
104
- return undefined;
105
- return this.opts.plans.getPlan(user.currentPlanId);
106
- }
107
- }
108
- exports.UsageService = UsageService;
@@ -1,82 +0,0 @@
1
- export type SubscriptionStatus = 'active' | 'trialing' | 'past_due' | 'canceled' | 'unpaid' | 'incomplete' | 'paused';
2
- export type PaymentStatus = 'succeeded' | 'failed' | 'pending' | 'refunded';
3
- export interface UserSubscription {
4
- id: string;
5
- userId: string;
6
- planId: string;
7
- status: SubscriptionStatus;
8
- /** Provider-specific subscription ID (e.g., Stripe sub_xxx) */
9
- providerSubscriptionId?: string;
10
- /** Provider-specific customer ID (e.g., Stripe cus_xxx) */
11
- providerCustomerId?: string;
12
- currentPeriodStart: Date;
13
- currentPeriodEnd: Date;
14
- trialEnd?: Date;
15
- cancelAtPeriodEnd: boolean;
16
- createdAt: Date;
17
- updatedAt: Date;
18
- }
19
- export interface UsageSummary {
20
- userId: string;
21
- planId: string;
22
- periodStart: Date;
23
- periodEnd: Date;
24
- requestsUsed: number;
25
- tokensUsed: number;
26
- creditsUsed?: number;
27
- requestsLimit: number;
28
- tokensLimit: number;
29
- creditsBalance?: number;
30
- percentUsed: {
31
- requests: number;
32
- tokens: number;
33
- };
34
- isOverLimit: boolean;
35
- }
36
- export interface UsageRecord {
37
- id: string;
38
- userId: string;
39
- conversationId: string;
40
- messageId: string;
41
- agentId: string;
42
- inputTokens: number;
43
- outputTokens: number;
44
- totalTokens: number;
45
- requestCount: number;
46
- creditsConsumed?: number;
47
- costInCents?: number;
48
- billingPeriod: string;
49
- recordedAt: Date;
50
- }
51
- export interface CheckoutResult {
52
- sessionId: string;
53
- url: string;
54
- planId: string;
55
- userId: string;
56
- }
57
- export interface PortalResult {
58
- url: string;
59
- }
60
- export interface BillingOverviewResult {
61
- subscription: UserSubscription | null;
62
- usage: UsageSummary;
63
- plan: {
64
- id: string;
65
- name: string;
66
- features: string[];
67
- limits: {
68
- requestsPerMonth: number;
69
- tokensPerMonth: number;
70
- };
71
- };
72
- invoiceHistory?: InvoiceRecord[];
73
- }
74
- export interface InvoiceRecord {
75
- id: string;
76
- amount: number;
77
- currency: string;
78
- status: string;
79
- createdAt: Date;
80
- pdfUrl?: string;
81
- hostedUrl?: string;
82
- }
@@ -1,3 +0,0 @@
1
- "use strict";
2
- // ─── Billing Runtime Types ────────────────────────────────────────────────────
3
- Object.defineProperty(exports, "__esModule", { value: true });