@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
package/dist/domain/agent.d.ts
DELETED
|
@@ -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'>>;
|
package/dist/domain/agent.js
DELETED
package/dist/domain/api-key.d.ts
DELETED
|
@@ -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'>>;
|
package/dist/domain/api-key.js
DELETED
|
@@ -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,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'>>;
|
package/dist/domain/plan.d.ts
DELETED
|
@@ -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 };
|
package/dist/domain/plan.js
DELETED
|
@@ -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,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'>>;
|
package/dist/domain/tenant.d.ts
DELETED
|
@@ -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'>>;
|
package/dist/domain/tenant.js
DELETED
|
@@ -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'>;
|
package/dist/domain/user.d.ts
DELETED
|
@@ -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'>>;
|
package/dist/domain/user.js
DELETED
|
@@ -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;
|