@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.
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 +57 -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,59 +0,0 @@
1
- import type { McpServerConfig } from '../types/config.types';
2
- /**
3
- * Visual customization shared by every embed of an agent — the public widget
4
- * (`/widget.js`) and the iframe view both read this. Lives on the agent so a
5
- * single edit ripples through every site that's embedded it; per-token
6
- * theming is intentionally NOT supported (was a misfeature). All fields
7
- * optional — widget falls back to its built-in defaults for anything omitted.
8
- */
9
- export interface AgentAppearance {
10
- primaryColor?: string;
11
- position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
12
- title?: string;
13
- greeting?: string;
14
- avatarUrl?: string;
15
- }
16
- /**
17
- * Persisted agent configuration scoped to a Tenant. Created and edited from
18
- * the admin UI; resolved by the SDK at runtime via `(tenantId, slug)`.
19
- *
20
- * Shape is intentionally a superset of `AgentDefinition` (the in-process
21
- * config object used today) plus DB metadata. The orchestrator/runner only
22
- * needs the prompt/model/limits/tools — everything else here is for
23
- * lifecycle: who created it, when, is it still active.
24
- */
25
- export interface AgentRecord {
26
- id: string;
27
- /** Owner tenant — every lookup is scoped to this. */
28
- tenantId: string;
29
- /** Human-friendly identifier the SDK uses, e.g. `support-bot`.
30
- * Unique within a tenant, not globally. */
31
- slug: string;
32
- name: string;
33
- description?: string;
34
- /** Claude model id, e.g. `claude-sonnet-4-6`. */
35
- model?: string;
36
- systemPrompt: string;
37
- /** Optional plain-text knowledge prepended to the system prompt at run-time.
38
- * No retrieval — this is the cheap path before RAG. */
39
- context?: string;
40
- temperature?: number;
41
- topP?: number;
42
- maxTokens?: number;
43
- /** Tool ids the agent can use. Persisted but not yet exposed in the UI. */
44
- tools?: string[];
45
- /** MCP servers the agent can connect to. Same caveat as `tools`. */
46
- mcpServers?: McpServerConfig[];
47
- /** Visual customization for embeds. Single source of truth — applies to
48
- * every chat token issued for this agent. */
49
- appearance?: AgentAppearance;
50
- /** Soft-delete: keeps history intact while hiding from active use. */
51
- isActive: boolean;
52
- /** UserId of the platform user who created the agent (audit trail). */
53
- createdByUserId?: string;
54
- metadata?: Record<string, unknown>;
55
- createdAt: Date;
56
- updatedAt: Date;
57
- }
58
- export type NewAgentRecord = Pick<AgentRecord, 'id' | 'tenantId' | 'slug' | 'name' | 'systemPrompt'> & Partial<Omit<AgentRecord, 'id' | 'tenantId' | 'slug' | 'name' | 'systemPrompt' | 'createdAt' | 'updatedAt'>>;
59
- export type AgentRecordPatch = Partial<Omit<AgentRecord, 'id' | 'tenantId' | 'createdAt' | 'updatedAt'>>;
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,28 +0,0 @@
1
- /**
2
- * Long-lived secret a tenant uses to authenticate machine-to-machine. Format:
3
- * ak_live_<48 hex chars> (192 bits of entropy)
4
- *
5
- * The full key is shown to the user ONLY at creation time. We store the
6
- * sha256 hash. The first 16 chars (`ak_live_a1b2c3d4`) are kept in plaintext
7
- * as a `keyPrefix` so users can identify which key in their dashboard.
8
- *
9
- * The `ak_live_` prefix is intentional — GitHub secret-scanning, Datadog and
10
- * similar tools detect leaked credentials by prefix.
11
- */
12
- export interface ApiKey {
13
- id: string;
14
- tenantId: string;
15
- /** When set, the key only authorizes calls to this specific agent. NULL
16
- * means tenant-wide — any agent in the tenant resolves. */
17
- agentId?: string;
18
- name: string;
19
- keyHash: string;
20
- keyPrefix: string;
21
- scopes?: string[];
22
- expiresAt?: Date;
23
- revokedAt?: Date;
24
- lastUsedAt?: Date;
25
- createdAt: Date;
26
- }
27
- export type NewApiKey = Omit<ApiKey, 'id' | 'createdAt' | 'lastUsedAt'>;
28
- export type ApiKeyPatch = Partial<Pick<ApiKey, 'lastUsedAt' | 'revokedAt' | 'expiresAt' | 'name'>>;
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,10 +0,0 @@
1
- export interface AuthIdentity {
2
- id: string;
3
- userId: string;
4
- provider: string;
5
- providerId: string;
6
- email?: string;
7
- metadata?: Record<string, unknown>;
8
- createdAt: Date;
9
- }
10
- export type NewAuthIdentity = Omit<AuthIdentity, 'id' | 'createdAt'>;
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,11 +0,0 @@
1
- export type EmailTokenPurpose = 'verify_email' | 'reset_password';
2
- export interface EmailToken {
3
- id: string;
4
- userId: string;
5
- tokenHash: string;
6
- purpose: EmailTokenPurpose;
7
- expiresAt: Date;
8
- consumedAt?: Date;
9
- createdAt: Date;
10
- }
11
- export type NewEmailToken = Omit<EmailToken, 'id' | 'createdAt'>;
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,23 +0,0 @@
1
- /**
2
- * Map of (tenantId, externalId) → internal AgentForge userId.
3
- *
4
- * In B2B mode, the consumer service has its own user system. When it calls
5
- * AgentForge with `X-On-Behalf-Of: <theirUserId>`, we look up (or create) an
6
- * `ExternalUser` row. The internal `id` is what we store on conversations,
7
- * messages, usage records — so the rest of the system runs unchanged.
8
- *
9
- * Two consumers using "user_42" as an externalId get DIFFERENT internal ids,
10
- * because the (tenantId, externalId) tuple is unique.
11
- */
12
- export interface ExternalUser {
13
- /** Internal AgentForge UUID (the userId for everything downstream). */
14
- id: string;
15
- tenantId: string;
16
- /** The consumer's own user identifier — opaque to AgentForge. */
17
- externalId: string;
18
- email?: string;
19
- metadata?: Record<string, unknown>;
20
- createdAt: Date;
21
- updatedAt: Date;
22
- }
23
- export type NewExternalUser = Pick<ExternalUser, 'id' | 'tenantId' | 'externalId'> & Partial<Pick<ExternalUser, 'email' | 'metadata'>>;
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,20 +0,0 @@
1
- import type { PlanDefinition, UsageLimits } from '../types/config.types';
2
- /**
3
- * A persisted plan row. Plans live in the DB so admins can edit
4
- * limits/features/prices at runtime. The container seeds the table from
5
- * `config.billing.plans` on first boot (when the table is empty), so existing
6
- * deployments don't break; after that, the DB is the source of truth.
7
- */
8
- export interface Plan extends PlanDefinition {
9
- /** Soft delete flag — keeps the row for historical subscriptions. */
10
- isActive: boolean;
11
- createdAt: Date;
12
- updatedAt: Date;
13
- }
14
- export type NewPlan = Pick<PlanDefinition, 'id' | 'name' | 'limits'> & Partial<Omit<PlanDefinition, 'id' | 'name' | 'limits'>> & {
15
- isActive?: boolean;
16
- };
17
- export type PlanPatch = Partial<Omit<PlanDefinition, 'id'> & {
18
- isActive: boolean;
19
- }>;
20
- export type { UsageLimits };
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,24 +0,0 @@
1
- export interface PlatformSecret {
2
- id: string;
3
- /** Stable identifier matching the canonical env-var name. */
4
- key: string;
5
- /** Encrypted blob: AES-GCM `iv | ciphertext | tag`. Persisted as bytea. */
6
- valueEncrypted: Buffer;
7
- /** Last 4 chars of the plaintext (or "••••" when too short). UI display. */
8
- valueHint: string;
9
- /** Free-form description shown next to the field in the admin UI. */
10
- description?: string;
11
- /** User who last wrote this row. Optional because the first import from
12
- * .env may have no user attached. */
13
- updatedByUserId?: string;
14
- createdAt: Date;
15
- updatedAt: Date;
16
- }
17
- export interface NewPlatformSecret {
18
- key: string;
19
- valueEncrypted: Buffer;
20
- valueHint: string;
21
- description?: string;
22
- updatedByUserId?: string;
23
- }
24
- export type PlatformSecretPatch = Partial<Pick<PlatformSecret, 'valueEncrypted' | 'valueHint' | 'description' | 'updatedByUserId'>>;
@@ -1,8 +0,0 @@
1
- "use strict";
2
- // Domain type for platform-wide secrets (Tavily/Brave/Stripe/Anthropic).
3
- //
4
- // Note what's MISSING: the plaintext value. The plaintext only exists at
5
- // two moments in the system's life — when the operator types it into the
6
- // UI, and during the boot-time decrypt inside SecretsService. Anything
7
- // else (repos, API responses, logs) must work off the hint + ciphertext.
8
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,15 +0,0 @@
1
- export interface RefreshToken {
2
- id: string;
3
- userId: string;
4
- tokenHash: string;
5
- family: string;
6
- expiresAt: Date;
7
- revokedAt?: Date;
8
- replacedById?: string;
9
- userAgent?: string;
10
- ip?: string;
11
- createdAt: Date;
12
- lastUsedAt?: Date;
13
- }
14
- export type NewRefreshToken = Omit<RefreshToken, 'id' | 'createdAt'>;
15
- export type RefreshTokenPatch = Partial<Omit<RefreshToken, 'id' | 'createdAt'>>;
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,21 +0,0 @@
1
- import type { SubscriptionStatus } from '../types/billing.types';
2
- export interface Subscription {
3
- id: string;
4
- /** End-user subscription. Set for B2C; may be null in B2B mode where the
5
- * payer is the tenant, not an individual user. */
6
- userId?: string;
7
- /** Tenant subscription. Set in B2B mode; null for B2C/end-user subs. */
8
- tenantId?: string;
9
- planId: string;
10
- status: SubscriptionStatus;
11
- providerSubscriptionId?: string;
12
- providerCustomerId?: string;
13
- currentPeriodStart?: Date;
14
- currentPeriodEnd?: Date;
15
- trialEnd?: Date;
16
- cancelAtPeriodEnd: boolean;
17
- createdAt: Date;
18
- updatedAt: Date;
19
- }
20
- export type NewSubscription = Omit<Subscription, 'id' | 'createdAt' | 'updatedAt'>;
21
- export type SubscriptionPatch = Partial<Omit<Subscription, 'id' | 'createdAt' | 'updatedAt'>>;
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,21 +0,0 @@
1
- /**
2
- * A Tenant (a.k.a. "client") is a consumer service that signed up to use
3
- * AgentForge as a backend. Every B2B request carries an API key that resolves
4
- * to one Tenant, and all data is scoped under it. The Tenant's `ownerUserId`
5
- * points at a User row (the human admin who manages the tenant).
6
- */
7
- export interface Tenant {
8
- id: string;
9
- name: string;
10
- /** UserId of the human admin who manages this tenant. */
11
- ownerUserId: string;
12
- planId: string;
13
- /** Stripe (or other provider) customer id — lazy-created on first checkout. */
14
- providerCustomerId?: string;
15
- metadata?: Record<string, unknown>;
16
- isActive: boolean;
17
- createdAt: Date;
18
- updatedAt: Date;
19
- }
20
- export type NewTenant = Pick<Tenant, 'id' | 'name' | 'ownerUserId'> & Partial<Omit<Tenant, 'id' | 'name' | 'ownerUserId' | 'createdAt' | 'updatedAt'>>;
21
- export type TenantPatch = Partial<Omit<Tenant, 'id' | 'createdAt' | 'updatedAt'>>;
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,15 +0,0 @@
1
- export interface UsageRecord {
2
- id: string;
3
- userId: string;
4
- conversationId: string;
5
- messageId: string;
6
- agentId: string;
7
- inputTokens: number;
8
- outputTokens: number;
9
- totalTokens: number;
10
- requestCount: number;
11
- /** YYYY-MM bucket used by quota counters. */
12
- billingPeriod: string;
13
- recordedAt: Date;
14
- }
15
- export type NewUsageRecord = Omit<UsageRecord, 'id'>;
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,43 +0,0 @@
1
- /**
2
- * Roles in AgentForge's RBAC.
3
- *
4
- * - `user` — default. Can own tenants, manage their own API keys,
5
- * view their billing. Bulk of signups land here.
6
- * - `platform_admin` — operator of the AgentForge instance. Can edit the
7
- * plan catalog, manage users, and any other endpoint
8
- * that operates at instance scope.
9
- *
10
- * Bootstrap: the very first user registered on a fresh install is auto-
11
- * promoted to `platform_admin` (since there's nobody else to grant it).
12
- * Subsequent users default to `user`; existing platform admins can promote
13
- * them via the admin UI.
14
- */
15
- export type UserRole = 'user' | 'platform_admin';
16
- /**
17
- * Plain domain shape for a User. Mirrors the columns of `af_users` but is
18
- * deliberately decoupled from TypeORM (no decorators, no `@Entity`). Each
19
- * adapter is free to back this with TypeORM, Prisma, Drizzle, raw SQL, etc.
20
- */
21
- export interface User {
22
- id: string;
23
- email?: string;
24
- name?: string;
25
- passwordHash?: string;
26
- authProvider?: string;
27
- providerCustomerId?: string;
28
- currentPlanId: string;
29
- creditsBalance: number;
30
- /** RBAC role. See {@link UserRole} for semantics. */
31
- role: UserRole;
32
- externalId?: string;
33
- metadata?: Record<string, unknown>;
34
- isActive: boolean;
35
- failedLoginCount: number;
36
- lockedUntil?: Date;
37
- lastLoginAt?: Date;
38
- emailVerifiedAt?: Date;
39
- createdAt: Date;
40
- updatedAt: Date;
41
- }
42
- export type NewUser = Pick<User, 'id'> & Partial<Omit<User, 'id' | 'createdAt' | 'updatedAt'>>;
43
- export type UserPatch = Partial<Omit<User, 'id' | 'createdAt' | 'updatedAt'>>;
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,45 +0,0 @@
1
- import type { AgentConfigRepository, AgentConfigListOptions, AgentConfigListResult } from '../repositories';
2
- import type { AgentRecord, AgentRecordPatch } from '../domain/agent';
3
- export declare class AgentConfigError extends Error {
4
- status: number;
5
- code: 'not_found' | 'duplicate_slug' | 'invalid' | 'forbidden';
6
- constructor(code: AgentConfigError['code'], message: string);
7
- }
8
- export interface AgentConfigCreateInput {
9
- tenantId: string;
10
- slug: string;
11
- name: string;
12
- systemPrompt: string;
13
- description?: string;
14
- model?: string;
15
- context?: string;
16
- temperature?: number;
17
- topP?: number;
18
- maxTokens?: number;
19
- tools?: string[];
20
- isActive?: boolean;
21
- createdByUserId?: string;
22
- metadata?: Record<string, unknown>;
23
- }
24
- /**
25
- * Application-level CRUD for per-tenant agent configurations. Controllers
26
- * call this — they do NOT touch the repository directly so we can keep
27
- * validation (slug format, unique-per-tenant) in one place.
28
- */
29
- export declare class AgentConfigService {
30
- private readonly repo;
31
- constructor(repo: AgentConfigRepository);
32
- create(input: AgentConfigCreateInput): Promise<AgentRecord>;
33
- getById(id: string): Promise<AgentRecord>;
34
- /**
35
- * Confirms the agent belongs to `tenantId`. Used by admin endpoints so a
36
- * tenant can't fetch another tenant's agent by guessing the id.
37
- */
38
- getByIdForTenant(id: string, tenantId: string): Promise<AgentRecord>;
39
- /** SDK hot path. Returns null on miss — callers decide whether to 404 or
40
- * fall back to the legacy hardcoded array. */
41
- findBySlug(tenantId: string, slug: string): Promise<AgentRecord | null>;
42
- listForTenant(tenantId: string, opts?: AgentConfigListOptions): Promise<AgentConfigListResult>;
43
- update(id: string, tenantId: string, patch: AgentRecordPatch): Promise<AgentRecord>;
44
- deactivate(id: string, tenantId: string): Promise<void>;
45
- }
@@ -1,114 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AgentConfigService = exports.AgentConfigError = void 0;
4
- const crypto_1 = require("crypto");
5
- class AgentConfigError extends Error {
6
- constructor(code, message) {
7
- super(message);
8
- this.code = code;
9
- this.status =
10
- code === 'forbidden'
11
- ? 403
12
- : code === 'not_found'
13
- ? 404
14
- : code === 'duplicate_slug'
15
- ? 409
16
- : 400;
17
- }
18
- }
19
- exports.AgentConfigError = AgentConfigError;
20
- // Slug rules are deliberately strict so they're URL-safe and predictable for
21
- // SDK callers: lowercase letters, digits, hyphen, underscore. Length 2..64.
22
- const SLUG_RE = /^[a-z0-9][a-z0-9_-]{1,62}[a-z0-9]$/;
23
- /**
24
- * Application-level CRUD for per-tenant agent configurations. Controllers
25
- * call this — they do NOT touch the repository directly so we can keep
26
- * validation (slug format, unique-per-tenant) in one place.
27
- */
28
- class AgentConfigService {
29
- constructor(repo) {
30
- this.repo = repo;
31
- }
32
- async create(input) {
33
- if (!input.tenantId)
34
- throw new AgentConfigError('invalid', 'tenantId is required');
35
- if (!input.name?.trim())
36
- throw new AgentConfigError('invalid', 'name is required');
37
- if (!input.systemPrompt?.trim())
38
- throw new AgentConfigError('invalid', 'systemPrompt is required');
39
- const slug = input.slug.trim().toLowerCase();
40
- if (!SLUG_RE.test(slug)) {
41
- throw new AgentConfigError('invalid', 'slug must be 3–64 chars, lowercase letters/digits/hyphen/underscore, start and end alphanumeric');
42
- }
43
- const existing = await this.repo.findBySlug(input.tenantId, slug);
44
- if (existing) {
45
- throw new AgentConfigError('duplicate_slug', `An agent with slug "${slug}" already exists for this tenant.`);
46
- }
47
- const record = {
48
- id: (0, crypto_1.randomUUID)(),
49
- tenantId: input.tenantId,
50
- slug,
51
- name: input.name.trim(),
52
- systemPrompt: input.systemPrompt,
53
- description: input.description?.trim(),
54
- model: input.model,
55
- context: input.context,
56
- temperature: input.temperature,
57
- topP: input.topP,
58
- maxTokens: input.maxTokens,
59
- tools: input.tools,
60
- isActive: input.isActive ?? true,
61
- createdByUserId: input.createdByUserId,
62
- metadata: input.metadata,
63
- };
64
- return this.repo.create(record);
65
- }
66
- async getById(id) {
67
- const a = await this.repo.findById(id);
68
- if (!a)
69
- throw new AgentConfigError('not_found', 'Agent not found');
70
- return a;
71
- }
72
- /**
73
- * Confirms the agent belongs to `tenantId`. Used by admin endpoints so a
74
- * tenant can't fetch another tenant's agent by guessing the id.
75
- */
76
- async getByIdForTenant(id, tenantId) {
77
- const a = await this.getById(id);
78
- if (a.tenantId !== tenantId) {
79
- // Don't reveal existence — return not_found.
80
- throw new AgentConfigError('not_found', 'Agent not found');
81
- }
82
- return a;
83
- }
84
- /** SDK hot path. Returns null on miss — callers decide whether to 404 or
85
- * fall back to the legacy hardcoded array. */
86
- async findBySlug(tenantId, slug) {
87
- return this.repo.findBySlug(tenantId, slug.toLowerCase());
88
- }
89
- async listForTenant(tenantId, opts = {}) {
90
- return this.repo.listForTenant(tenantId, opts);
91
- }
92
- async update(id, tenantId, patch) {
93
- const current = await this.getByIdForTenant(id, tenantId);
94
- // Slug changes need re-validation + uniqueness check.
95
- if (patch.slug !== undefined && patch.slug !== current.slug) {
96
- const nextSlug = patch.slug.trim().toLowerCase();
97
- if (!SLUG_RE.test(nextSlug)) {
98
- throw new AgentConfigError('invalid', 'slug must be 3–64 chars, lowercase letters/digits/hyphen/underscore');
99
- }
100
- const dup = await this.repo.findBySlug(tenantId, nextSlug);
101
- if (dup && dup.id !== id) {
102
- throw new AgentConfigError('duplicate_slug', `An agent with slug "${nextSlug}" already exists for this tenant.`);
103
- }
104
- patch.slug = nextSlug;
105
- }
106
- await this.repo.update(id, patch);
107
- return this.getById(id);
108
- }
109
- async deactivate(id, tenantId) {
110
- await this.getByIdForTenant(id, tenantId);
111
- await this.repo.deactivate(id);
112
- }
113
- }
114
- exports.AgentConfigService = AgentConfigService;
@@ -1,41 +0,0 @@
1
- import type { ApiKeyRepository } from '../repositories';
2
- import type { ApiKey } from '../domain/api-key';
3
- export interface MintedApiKey {
4
- /** Plaintext key. Only available at creation time — never persisted. */
5
- key: string;
6
- entity: ApiKey;
7
- }
8
- /** Operational errors. Adapters map `.status` to an HTTP code. */
9
- export declare class ApiKeyError extends Error {
10
- status: number;
11
- code: 'invalid' | 'revoked' | 'expired';
12
- constructor(code: 'invalid' | 'revoked' | 'expired', message: string);
13
- }
14
- export declare class ApiKeyService {
15
- private readonly repo;
16
- constructor(repo: ApiKeyRepository);
17
- /** Mint a new API key for a tenant. Returns the plaintext key (one-time).
18
- * Pass `agentId` to scope the key to a specific agent in the tenant —
19
- * calls with that key only resolve when the requested agent matches. */
20
- issue(input: {
21
- tenantId: string;
22
- agentId?: string;
23
- name: string;
24
- scopes?: string[];
25
- expiresAt?: Date;
26
- }): Promise<MintedApiKey>;
27
- /**
28
- * Validate a presented key. Returns the matching ApiKey row (without
29
- * touching the plaintext) or throws ApiKeyError. Updates lastUsedAt as a
30
- * best-effort side-effect (never blocks on the write).
31
- */
32
- validate(rawKey: string): Promise<ApiKey>;
33
- listForTenant(tenantId: string): Promise<ApiKey[]>;
34
- listForAgent(tenantId: string, agentId: string): Promise<ApiKey[]>;
35
- revoke(id: string): Promise<void>;
36
- /** Tells callers (HTTP middleware) whether a header value looks like an API key. */
37
- static isApiKeyToken(token: string | undefined | null): boolean;
38
- private hash;
39
- }
40
- /** Convenience for `randomUUID` access from external callers. */
41
- export declare const newApiKeyId: () => `${string}-${string}-${string}-${string}-${string}`;
@@ -1,80 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.newApiKeyId = exports.ApiKeyService = exports.ApiKeyError = void 0;
4
- const crypto_1 = require("crypto");
5
- const API_KEY_PREFIX_LIVE = 'ak_live_';
6
- /** Operational errors. Adapters map `.status` to an HTTP code. */
7
- class ApiKeyError extends Error {
8
- constructor(code, message) {
9
- super(message);
10
- this.code = code;
11
- this.status = 401;
12
- }
13
- }
14
- exports.ApiKeyError = ApiKeyError;
15
- class ApiKeyService {
16
- constructor(repo) {
17
- this.repo = repo;
18
- }
19
- /** Mint a new API key for a tenant. Returns the plaintext key (one-time).
20
- * Pass `agentId` to scope the key to a specific agent in the tenant —
21
- * calls with that key only resolve when the requested agent matches. */
22
- async issue(input) {
23
- const random = (0, crypto_1.randomBytes)(24).toString('hex'); // 192-bit
24
- const key = `${API_KEY_PREFIX_LIVE}${random}`;
25
- const keyHash = this.hash(key);
26
- const keyPrefix = key.slice(0, 16); // "ak_live_a1b2c3d4"
27
- const entity = await this.repo.create({
28
- tenantId: input.tenantId,
29
- agentId: input.agentId,
30
- name: input.name,
31
- keyHash,
32
- keyPrefix,
33
- scopes: input.scopes,
34
- expiresAt: input.expiresAt,
35
- });
36
- return { key, entity };
37
- }
38
- /**
39
- * Validate a presented key. Returns the matching ApiKey row (without
40
- * touching the plaintext) or throws ApiKeyError. Updates lastUsedAt as a
41
- * best-effort side-effect (never blocks on the write).
42
- */
43
- async validate(rawKey) {
44
- if (!rawKey?.startsWith(API_KEY_PREFIX_LIVE)) {
45
- throw new ApiKeyError('invalid', 'Invalid API key');
46
- }
47
- const keyHash = this.hash(rawKey);
48
- const apiKey = await this.repo.findByHash(keyHash);
49
- if (!apiKey)
50
- throw new ApiKeyError('invalid', 'Invalid API key');
51
- if (apiKey.revokedAt)
52
- throw new ApiKeyError('revoked', 'API key has been revoked');
53
- if (apiKey.expiresAt && apiKey.expiresAt <= new Date()) {
54
- throw new ApiKeyError('expired', 'API key has expired');
55
- }
56
- // Best-effort lastUsedAt update — fire and forget.
57
- this.repo.update(apiKey.id, { lastUsedAt: new Date() }).catch(() => { });
58
- return apiKey;
59
- }
60
- async listForTenant(tenantId) {
61
- return this.repo.listByTenant(tenantId);
62
- }
63
- async listForAgent(tenantId, agentId) {
64
- return this.repo.listByAgent(tenantId, agentId);
65
- }
66
- async revoke(id) {
67
- await this.repo.update(id, { revokedAt: new Date() });
68
- }
69
- /** Tells callers (HTTP middleware) whether a header value looks like an API key. */
70
- static isApiKeyToken(token) {
71
- return !!token && token.startsWith(API_KEY_PREFIX_LIVE);
72
- }
73
- hash(raw) {
74
- return (0, crypto_1.createHash)('sha256').update(raw).digest('hex');
75
- }
76
- }
77
- exports.ApiKeyService = ApiKeyService;
78
- /** Convenience for `randomUUID` access from external callers. */
79
- const newApiKeyId = () => (0, crypto_1.randomUUID)();
80
- exports.newApiKeyId = newApiKeyId;