@agentforge-io/core 2.0.24 → 2.1.1
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.
- package/dist/factory.js +56 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +7 -1
- package/dist/services/agent-runner.service.js +57 -4
- package/dist/services/agent.service.d.ts +21 -1
- package/dist/services/agent.service.js +42 -8
- package/dist/services/orchestrator.service.d.ts +40 -1
- package/dist/services/orchestrator.service.js +220 -0
- package/dist/types/agent.types.d.ts +31 -6
- package/package.json +1 -1
- package/dist/adapters/billing/billing-adapter.interface.d.ts +0 -41
- package/dist/adapters/billing/billing-adapter.interface.js +0 -5
- package/dist/adapters/billing/stripe/stripe.adapter.d.ts +0 -30
- package/dist/adapters/billing/stripe/stripe.adapter.js +0 -122
- package/dist/adapters/email/email-adapter.interface.d.ts +0 -25
- package/dist/adapters/email/email-adapter.interface.js +0 -6
- package/dist/adapters/email/noop.adapter.d.ts +0 -10
- package/dist/adapters/email/noop.adapter.js +0 -15
- package/dist/adapters/email/resend.adapter.d.ts +0 -8
- package/dist/adapters/email/resend.adapter.js +0 -39
- package/dist/adapters/upload/noop.adapter.d.ts +0 -9
- package/dist/adapters/upload/noop.adapter.js +0 -14
- package/dist/adapters/upload/s3.adapter.d.ts +0 -38
- package/dist/adapters/upload/s3.adapter.js +0 -69
- package/dist/adapters/upload/upload-adapter.interface.d.ts +0 -37
- package/dist/adapters/upload/upload-adapter.interface.js +0 -15
- package/dist/billing/index.d.ts +0 -12
- package/dist/billing/index.js +0 -28
- package/dist/domain/agent.d.ts +0 -59
- package/dist/domain/agent.js +0 -2
- package/dist/domain/api-key.d.ts +0 -28
- package/dist/domain/api-key.js +0 -2
- package/dist/domain/auth-identity.d.ts +0 -10
- package/dist/domain/auth-identity.js +0 -2
- package/dist/domain/email-token.d.ts +0 -11
- package/dist/domain/email-token.js +0 -2
- package/dist/domain/external-user.d.ts +0 -23
- package/dist/domain/external-user.js +0 -2
- package/dist/domain/plan.d.ts +0 -20
- package/dist/domain/plan.js +0 -2
- package/dist/domain/platform-secret.d.ts +0 -24
- package/dist/domain/platform-secret.js +0 -8
- package/dist/domain/refresh-token.d.ts +0 -15
- package/dist/domain/refresh-token.js +0 -2
- package/dist/domain/subscription.d.ts +0 -21
- package/dist/domain/subscription.js +0 -2
- package/dist/domain/tenant.d.ts +0 -21
- package/dist/domain/tenant.js +0 -2
- package/dist/domain/usage-record.d.ts +0 -15
- package/dist/domain/usage-record.js +0 -2
- package/dist/domain/user.d.ts +0 -43
- package/dist/domain/user.js +0 -2
- package/dist/services/agent-config.service.d.ts +0 -45
- package/dist/services/agent-config.service.js +0 -114
- package/dist/services/api-key.service.d.ts +0 -41
- package/dist/services/api-key.service.js +0 -80
- package/dist/services/auth.service.d.ts +0 -133
- package/dist/services/auth.service.js +0 -411
- package/dist/services/billing.service.d.ts +0 -67
- package/dist/services/billing.service.js +0 -254
- package/dist/services/email-templates.d.ts +0 -18
- package/dist/services/email-templates.js +0 -39
- package/dist/services/email.service.d.ts +0 -26
- package/dist/services/email.service.js +0 -42
- package/dist/services/errors.d.ts +0 -7
- package/dist/services/errors.js +0 -27
- package/dist/services/oauth.service.d.ts +0 -73
- package/dist/services/oauth.service.js +0 -174
- package/dist/services/plan.service.d.ts +0 -54
- package/dist/services/plan.service.js +0 -120
- package/dist/services/refresh-token.service.d.ts +0 -38
- package/dist/services/refresh-token.service.js +0 -73
- package/dist/services/secrets/crypto.d.ts +0 -37
- package/dist/services/secrets/crypto.js +0 -110
- package/dist/services/secrets/known-keys.d.ts +0 -38
- package/dist/services/secrets/known-keys.js +0 -50
- package/dist/services/secrets.service.d.ts +0 -91
- package/dist/services/secrets.service.js +0 -193
- package/dist/services/tenant-billing.service.d.ts +0 -121
- package/dist/services/tenant-billing.service.js +0 -290
- package/dist/services/tenant.service.d.ts +0 -54
- package/dist/services/tenant.service.js +0 -96
- package/dist/services/upload.service.d.ts +0 -37
- package/dist/services/upload.service.js +0 -84
- package/dist/services/usage.service.d.ts +0 -34
- package/dist/services/usage.service.js +0 -108
- package/dist/types/billing.types.d.ts +0 -82
- 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
|
-
}
|