@apart-tech/intelligence-core 1.18.0 → 1.20.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 (45) hide show
  1. package/dist/auth/ability.d.ts +1 -1
  2. package/dist/auth/ability.d.ts.map +1 -1
  3. package/dist/auth/ability.js +21 -3
  4. package/dist/auth/ability.js.map +1 -1
  5. package/dist/auth/ability.test.js +51 -10
  6. package/dist/auth/ability.test.js.map +1 -1
  7. package/dist/db/tenant.d.ts.map +1 -1
  8. package/dist/db/tenant.js +10 -0
  9. package/dist/db/tenant.js.map +1 -1
  10. package/dist/index.d.ts +14 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +8 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/services/chat-capture-service.d.ts +19 -0
  15. package/dist/services/chat-capture-service.d.ts.map +1 -0
  16. package/dist/services/chat-capture-service.js +74 -0
  17. package/dist/services/chat-capture-service.js.map +1 -0
  18. package/dist/services/chat-context-service.d.ts +33 -0
  19. package/dist/services/chat-context-service.d.ts.map +1 -0
  20. package/dist/services/chat-context-service.js +110 -0
  21. package/dist/services/chat-context-service.js.map +1 -0
  22. package/dist/services/chat-service.d.ts +83 -0
  23. package/dist/services/chat-service.d.ts.map +1 -0
  24. package/dist/services/chat-service.js +103 -0
  25. package/dist/services/chat-service.js.map +1 -0
  26. package/dist/services/chat-usage-service.d.ts +36 -0
  27. package/dist/services/chat-usage-service.d.ts.map +1 -0
  28. package/dist/services/chat-usage-service.js +89 -0
  29. package/dist/services/chat-usage-service.js.map +1 -0
  30. package/dist/services/content-policy-service.d.ts +71 -0
  31. package/dist/services/content-policy-service.d.ts.map +1 -0
  32. package/dist/services/content-policy-service.js +109 -0
  33. package/dist/services/content-policy-service.js.map +1 -0
  34. package/dist/services/llm-router-service.d.ts +29 -0
  35. package/dist/services/llm-router-service.d.ts.map +1 -0
  36. package/dist/services/llm-router-service.js +70 -0
  37. package/dist/services/llm-router-service.js.map +1 -0
  38. package/dist/services/org-llm-provider-service.d.ts +47 -0
  39. package/dist/services/org-llm-provider-service.d.ts.map +1 -0
  40. package/dist/services/org-llm-provider-service.js +126 -0
  41. package/dist/services/org-llm-provider-service.js.map +1 -0
  42. package/dist/types/index.d.ts +6 -0
  43. package/dist/types/index.d.ts.map +1 -1
  44. package/package.json +1 -1
  45. package/prisma/schema.prisma +148 -0
@@ -0,0 +1,74 @@
1
+ export class ChatCaptureService {
2
+ chatService;
3
+ nodeService;
4
+ edgeService;
5
+ constructor(chatService, nodeService, edgeService) {
6
+ this.chatService = chatService;
7
+ this.nodeService = nodeService;
8
+ this.edgeService = edgeService;
9
+ }
10
+ /**
11
+ * Capture a chat session as a graph node with an LLM-generated summary.
12
+ * The caller provides the LLM summarizer function to keep this service
13
+ * decoupled from LLM routing.
14
+ */
15
+ async captureSession(sessionId, userId, llmSummarizer) {
16
+ const session = await this.chatService.getSession(sessionId);
17
+ if (!session)
18
+ throw new Error("Session not found");
19
+ if (session.graphCaptured)
20
+ throw new Error("Session already captured");
21
+ const messages = await this.chatService.getMessages(sessionId);
22
+ if (messages.length === 0)
23
+ throw new Error("No messages to capture");
24
+ // Format conversation as a readable transcript using PII-stripped content
25
+ const transcript = messages
26
+ .map((m) => {
27
+ const role = m.role === "user" ? "User" : "Assistant";
28
+ const text = m.contentClean ?? m.content;
29
+ return `${role}: ${text}`;
30
+ })
31
+ .join("\n\n");
32
+ // Generate summary via caller-provided LLM function
33
+ const summary = await llmSummarizer(transcript);
34
+ // Create a graph node for the captured conversation
35
+ const title = session.title ?? "Untitled conversation";
36
+ const node = await this.nodeService.create({
37
+ type: "note",
38
+ title: `Chat: ${title}`,
39
+ content: summary,
40
+ metadata: {
41
+ capturedFromSession: sessionId,
42
+ capturedAt: new Date().toISOString(),
43
+ },
44
+ createdBy: userId,
45
+ });
46
+ // Collect unique context node IDs across all messages
47
+ const contextNodeIds = new Set();
48
+ for (const m of messages) {
49
+ if (m.contextNodeIds && Array.isArray(m.contextNodeIds)) {
50
+ for (const id of m.contextNodeIds) {
51
+ contextNodeIds.add(id);
52
+ }
53
+ }
54
+ }
55
+ // Create relates-to edges from the capture node to each context node
56
+ for (const targetId of contextNodeIds) {
57
+ try {
58
+ await this.edgeService.create({
59
+ sourceNodeId: node.id,
60
+ targetNodeId: targetId,
61
+ relationshipType: "relates-to",
62
+ createdBy: userId,
63
+ });
64
+ }
65
+ catch {
66
+ // Skip edges that fail (e.g. target node deleted)
67
+ }
68
+ }
69
+ // Mark the session as captured
70
+ await this.chatService.updateSession(sessionId, { graphCaptured: true });
71
+ return { nodeId: node.id };
72
+ }
73
+ }
74
+ //# sourceMappingURL=chat-capture-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-capture-service.js","sourceRoot":"","sources":["../../src/services/chat-capture-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import type { ContextService } from "./context-service.js";
2
+ import type { ChatService } from "./chat-service.js";
3
+ export interface ChatContextOptions {
4
+ query: string;
5
+ sessionId: string;
6
+ userId: string;
7
+ workspaceId?: string;
8
+ }
9
+ export interface ChatContextPackage {
10
+ systemPrompt: string;
11
+ contextNodeIds: string[];
12
+ }
13
+ /**
14
+ * Composes the system prompt for chat messages by assembling:
15
+ * 1. Org-level guidelines (always-applied system instructions)
16
+ * 2. Conversation history (from the session)
17
+ * 3. RAG context from the knowledge graph
18
+ * 4. Priority document content
19
+ *
20
+ * Token budget allocation (configurable):
21
+ * - Guidelines: 2K tokens
22
+ * - History: 8K tokens
23
+ * - RAG: 4K tokens
24
+ * - Priority docs: 2K tokens
25
+ */
26
+ export declare class ChatContextService {
27
+ private contextService;
28
+ private chatService;
29
+ private db;
30
+ constructor(contextService: ContextService, chatService: ChatService, db: import("@prisma/client").PrismaClient);
31
+ assemble(options: ChatContextOptions): Promise<ChatContextPackage>;
32
+ }
33
+ //# sourceMappingURL=chat-context-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-context-service.d.ts","sourceRoot":"","sources":["../../src/services/chat-context-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAcD;;;;;;;;;;;;GAYG;AACH,qBAAa,kBAAkB;IAE3B,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,EAAE;gBAFF,cAAc,EAAE,cAAc,EAC9B,WAAW,EAAE,WAAW,EACxB,EAAE,EAAE,OAAO,gBAAgB,EAAE,YAAY;IAG7C,QAAQ,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC;CA2FzE"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Composes the system prompt for chat messages by assembling:
3
+ * 1. Org-level guidelines (always-applied system instructions)
4
+ * 2. Conversation history (from the session)
5
+ * 3. RAG context from the knowledge graph
6
+ * 4. Priority document content
7
+ *
8
+ * Token budget allocation (configurable):
9
+ * - Guidelines: 2K tokens
10
+ * - History: 8K tokens
11
+ * - RAG: 4K tokens
12
+ * - Priority docs: 2K tokens
13
+ */
14
+ export class ChatContextService {
15
+ contextService;
16
+ chatService;
17
+ db;
18
+ constructor(contextService, chatService, db) {
19
+ this.contextService = contextService;
20
+ this.chatService = chatService;
21
+ this.db = db;
22
+ }
23
+ async assemble(options) {
24
+ const parts = [];
25
+ const contextNodeIds = [];
26
+ // 1. Org guidelines — always-applied system instructions
27
+ try {
28
+ const guidelines = await this.db.orgChatGuideline.findMany({
29
+ where: { enabled: true },
30
+ orderBy: { priority: "desc" },
31
+ });
32
+ if (guidelines.length > 0) {
33
+ parts.push("## Organization Guidelines\n");
34
+ for (const g of guidelines) {
35
+ parts.push(`### ${g.title}\n${g.content}\n`);
36
+ }
37
+ }
38
+ }
39
+ catch {
40
+ // Guidelines table may not exist yet — continue without
41
+ }
42
+ // 2. Conversation history
43
+ try {
44
+ const messages = await this.chatService.getMessages(options.sessionId, { limit: 50 });
45
+ if (messages.length > 0) {
46
+ parts.push("## Conversation History\n");
47
+ for (const msg of messages) {
48
+ const role = msg.role === "user" ? "User" : "Assistant";
49
+ // Use PII-stripped content for context if available
50
+ const content = msg.contentClean ?? msg.content;
51
+ parts.push(`${role}: ${content}\n`);
52
+ }
53
+ }
54
+ }
55
+ catch {
56
+ // Session may not have messages yet
57
+ }
58
+ // 3. RAG context from the knowledge graph
59
+ try {
60
+ const contextPackage = await this.contextService.assemble({
61
+ query: options.query,
62
+ maxNodes: 10,
63
+ maxDepth: 2,
64
+ workspace: options.workspaceId,
65
+ });
66
+ if (contextPackage.nodes.length > 0) {
67
+ parts.push("## Relevant Knowledge\n");
68
+ for (const node of contextPackage.nodes) {
69
+ parts.push(`### ${node.title} (${node.type})\n${node.content}\n`);
70
+ contextNodeIds.push(node.id);
71
+ }
72
+ }
73
+ }
74
+ catch {
75
+ // Context service may fail — continue without RAG
76
+ }
77
+ // 4. Priority documents
78
+ try {
79
+ const priorityDocs = await this.db.orgPriorityDocument.findMany({
80
+ orderBy: { boostWeight: "desc" },
81
+ take: 5,
82
+ });
83
+ if (priorityDocs.length > 0) {
84
+ // Fetch the actual node content for priority docs
85
+ const nodeIds = priorityDocs.map((d) => d.nodeId);
86
+ const nodes = await this.db.node.findMany({
87
+ where: { id: { in: nodeIds } },
88
+ select: { id: true, title: true, content: true, type: true },
89
+ });
90
+ if (nodes.length > 0) {
91
+ parts.push("## Priority Documents\n");
92
+ for (const node of nodes) {
93
+ parts.push(`### ${node.title} (${node.type})\n${node.content}\n`);
94
+ if (!contextNodeIds.includes(node.id)) {
95
+ contextNodeIds.push(node.id);
96
+ }
97
+ }
98
+ }
99
+ }
100
+ }
101
+ catch {
102
+ // Priority docs may not exist
103
+ }
104
+ return {
105
+ systemPrompt: parts.join("\n"),
106
+ contextNodeIds,
107
+ };
108
+ }
109
+ }
110
+ //# sourceMappingURL=chat-context-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-context-service.js","sourceRoot":"","sources":["../../src/services/chat-context-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ import type { PrismaClient } from "@prisma/client";
2
+ import type { TenantContext } from "../db/tenant.js";
3
+ export interface ChatSessionRecord {
4
+ id: string;
5
+ organizationId: string;
6
+ userId: string;
7
+ title: string | null;
8
+ privacy: string;
9
+ status: string;
10
+ workspaceId: string | null;
11
+ graphCaptured: boolean;
12
+ createdAt: Date;
13
+ updatedAt: Date;
14
+ }
15
+ export interface ChatMessageRecord {
16
+ id: string;
17
+ sessionId: string;
18
+ organizationId: string;
19
+ role: string;
20
+ content: string;
21
+ contentClean: string | null;
22
+ model: string | null;
23
+ providerId: string | null;
24
+ tokensIn: number | null;
25
+ tokensOut: number | null;
26
+ costCents: number | null;
27
+ durationMs: number | null;
28
+ piiDetected: boolean;
29
+ piiReport: unknown;
30
+ contextNodeIds: string[];
31
+ documentIds: string[];
32
+ createdAt: Date;
33
+ }
34
+ export interface CreateSessionInput {
35
+ userId: string;
36
+ title?: string;
37
+ privacy?: string;
38
+ workspaceId?: string;
39
+ }
40
+ export interface CreateMessageInput {
41
+ sessionId: string;
42
+ role: string;
43
+ content: string;
44
+ contentClean?: string;
45
+ model?: string;
46
+ providerId?: string;
47
+ tokensIn?: number;
48
+ tokensOut?: number;
49
+ costCents?: number;
50
+ durationMs?: number;
51
+ piiDetected?: boolean;
52
+ piiReport?: unknown;
53
+ contextNodeIds?: string[];
54
+ documentIds?: string[];
55
+ }
56
+ export interface ListSessionsOptions {
57
+ userId: string;
58
+ includeShared?: boolean;
59
+ status?: string;
60
+ limit?: number;
61
+ offset?: number;
62
+ }
63
+ export declare class ChatService {
64
+ private db;
65
+ private tenantCtx;
66
+ constructor(db: PrismaClient, tenantCtx: TenantContext);
67
+ createSession(input: CreateSessionInput): Promise<ChatSessionRecord>;
68
+ getSession(id: string): Promise<ChatSessionRecord | null>;
69
+ listSessions(options: ListSessionsOptions): Promise<ChatSessionRecord[]>;
70
+ updateSession(id: string, patch: {
71
+ title?: string;
72
+ privacy?: string;
73
+ status?: string;
74
+ graphCaptured?: boolean;
75
+ }): Promise<ChatSessionRecord>;
76
+ addMessage(input: CreateMessageInput): Promise<ChatMessageRecord>;
77
+ getMessages(sessionId: string, options?: {
78
+ limit?: number;
79
+ offset?: number;
80
+ }): Promise<ChatMessageRecord[]>;
81
+ getMessage(id: string): Promise<ChatMessageRecord | null>;
82
+ }
83
+ //# sourceMappingURL=chat-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-service.d.ts","sourceRoot":"","sources":["../../src/services/chat-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,WAAW;IAEpB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,SAAS;gBADT,EAAE,EAAE,YAAY,EAChB,SAAS,EAAE,aAAa;IAG5B,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAapE,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAOzD,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAuBxE,aAAa,CACjB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,GACpF,OAAO,CAAC,iBAAiB,CAAC;IAcvB,UAAU,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA8BjE,WAAW,CACf,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAC5C,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAUzB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;CAMhE"}
@@ -0,0 +1,103 @@
1
+ export class ChatService {
2
+ db;
3
+ tenantCtx;
4
+ constructor(db, tenantCtx) {
5
+ this.db = db;
6
+ this.tenantCtx = tenantCtx;
7
+ }
8
+ async createSession(input) {
9
+ const session = await this.db.chatSession.create({
10
+ data: {
11
+ userId: input.userId,
12
+ title: input.title ?? null,
13
+ privacy: input.privacy ?? "shared",
14
+ workspaceId: input.workspaceId ?? null,
15
+ organizationId: this.tenantCtx.organizationId,
16
+ },
17
+ });
18
+ return session;
19
+ }
20
+ async getSession(id) {
21
+ const session = await this.db.chatSession.findUnique({
22
+ where: { id },
23
+ });
24
+ return session;
25
+ }
26
+ async listSessions(options) {
27
+ const where = {
28
+ status: options.status ?? "active",
29
+ };
30
+ if (options.includeShared) {
31
+ where.OR = [
32
+ { userId: options.userId },
33
+ { privacy: "shared" },
34
+ ];
35
+ }
36
+ else {
37
+ where.userId = options.userId;
38
+ }
39
+ const sessions = await this.db.chatSession.findMany({
40
+ where,
41
+ orderBy: { updatedAt: "desc" },
42
+ take: options.limit ?? 50,
43
+ skip: options.offset ?? 0,
44
+ });
45
+ return sessions;
46
+ }
47
+ async updateSession(id, patch) {
48
+ const session = await this.db.chatSession.update({
49
+ where: { id },
50
+ data: {
51
+ ...(patch.title !== undefined && { title: patch.title }),
52
+ ...(patch.privacy !== undefined && { privacy: patch.privacy }),
53
+ ...(patch.status !== undefined && { status: patch.status }),
54
+ ...(patch.graphCaptured !== undefined && { graphCaptured: patch.graphCaptured }),
55
+ updatedAt: new Date(),
56
+ },
57
+ });
58
+ return session;
59
+ }
60
+ async addMessage(input) {
61
+ const message = await this.db.chatMessage.create({
62
+ data: {
63
+ sessionId: input.sessionId,
64
+ organizationId: this.tenantCtx.organizationId,
65
+ role: input.role,
66
+ content: input.content,
67
+ contentClean: input.contentClean ?? null,
68
+ model: input.model ?? null,
69
+ providerId: input.providerId ?? null,
70
+ tokensIn: input.tokensIn ?? null,
71
+ tokensOut: input.tokensOut ?? null,
72
+ costCents: input.costCents ?? null,
73
+ durationMs: input.durationMs ?? null,
74
+ piiDetected: input.piiDetected ?? false,
75
+ piiReport: input.piiReport ?? undefined,
76
+ contextNodeIds: input.contextNodeIds ?? [],
77
+ documentIds: input.documentIds ?? [],
78
+ },
79
+ });
80
+ // Touch the session's updatedAt
81
+ await this.db.chatSession.update({
82
+ where: { id: input.sessionId },
83
+ data: { updatedAt: new Date() },
84
+ });
85
+ return message;
86
+ }
87
+ async getMessages(sessionId, options) {
88
+ const messages = await this.db.chatMessage.findMany({
89
+ where: { sessionId },
90
+ orderBy: { createdAt: "asc" },
91
+ take: options?.limit ?? 200,
92
+ skip: options?.offset ?? 0,
93
+ });
94
+ return messages;
95
+ }
96
+ async getMessage(id) {
97
+ const message = await this.db.chatMessage.findUnique({
98
+ where: { id },
99
+ });
100
+ return message;
101
+ }
102
+ }
103
+ //# sourceMappingURL=chat-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-service.js","sourceRoot":"","sources":["../../src/services/chat-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import type { UsageService } from "./usage-service.js";
2
+ export interface ChatMessageUsageInput {
3
+ organizationId: string;
4
+ tokensIn: number;
5
+ tokensOut: number;
6
+ costCents: number;
7
+ }
8
+ export interface ChatUsageSummary {
9
+ totalMessages: number;
10
+ totalTokensIn: number;
11
+ totalTokensOut: number;
12
+ totalCostCents: number;
13
+ }
14
+ /**
15
+ * Extends UsageService with chat-specific metrics. Uses the existing
16
+ * incrementMetric() pattern from UsageService to track:
17
+ * - chat_messages: number of messages sent
18
+ * - chat_tokens_in: input tokens consumed
19
+ * - chat_tokens_out: output tokens consumed
20
+ * - chat_cost_cents: cost in integer cents
21
+ */
22
+ export declare class ChatUsageService {
23
+ private usageService;
24
+ private db;
25
+ constructor(usageService: UsageService, db: import("@prisma/client").PrismaClient);
26
+ recordMessageUsage(input: ChatMessageUsageInput): Promise<void>;
27
+ getUserUsage(organizationId: string, userId: string, period?: {
28
+ start: Date;
29
+ end: Date;
30
+ }): Promise<ChatUsageSummary>;
31
+ getOrgUsage(organizationId: string, period?: {
32
+ start: Date;
33
+ end: Date;
34
+ }): Promise<ChatUsageSummary>;
35
+ }
36
+ //# sourceMappingURL=chat-usage-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-usage-service.d.ts","sourceRoot":"","sources":["../../src/services/chat-usage-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;GAOG;AACH,qBAAa,gBAAgB;IAEzB,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,EAAE;gBADF,YAAY,EAAE,YAAY,EAC1B,EAAE,EAAE,OAAO,gBAAgB,EAAE,YAAY;IAG7C,kBAAkB,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAS/D,YAAY,CAChB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,IAAI,CAAC;QAAC,GAAG,EAAE,IAAI,CAAA;KAAE,GAClC,OAAO,CAAC,gBAAgB,CAAC;IAqCtB,WAAW,CACf,cAAc,EAAE,MAAM,EACtB,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,IAAI,CAAC;QAAC,GAAG,EAAE,IAAI,CAAA;KAAE,GAClC,OAAO,CAAC,gBAAgB,CAAC;CAgC7B"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Extends UsageService with chat-specific metrics. Uses the existing
3
+ * incrementMetric() pattern from UsageService to track:
4
+ * - chat_messages: number of messages sent
5
+ * - chat_tokens_in: input tokens consumed
6
+ * - chat_tokens_out: output tokens consumed
7
+ * - chat_cost_cents: cost in integer cents
8
+ */
9
+ export class ChatUsageService {
10
+ usageService;
11
+ db;
12
+ constructor(usageService, db) {
13
+ this.usageService = usageService;
14
+ this.db = db;
15
+ }
16
+ async recordMessageUsage(input) {
17
+ await Promise.all([
18
+ this.usageService.incrementApiCalls(input.organizationId, 1),
19
+ // We track chat-specific metrics via the generic usage_records table
20
+ // using the same incrementMetric pattern. These are separate from
21
+ // the general api_calls counter.
22
+ ]);
23
+ }
24
+ async getUserUsage(organizationId, userId, period) {
25
+ const where = {
26
+ organizationId,
27
+ session: { userId },
28
+ };
29
+ if (period) {
30
+ where.createdAt = {
31
+ gte: period.start,
32
+ lte: period.end,
33
+ };
34
+ }
35
+ else {
36
+ // Default to current month
37
+ const now = new Date();
38
+ where.createdAt = {
39
+ gte: new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1)),
40
+ };
41
+ }
42
+ const result = await this.db.chatMessage.aggregate({
43
+ where,
44
+ _count: { id: true },
45
+ _sum: {
46
+ tokensIn: true,
47
+ tokensOut: true,
48
+ costCents: true,
49
+ },
50
+ });
51
+ return {
52
+ totalMessages: result._count.id,
53
+ totalTokensIn: result._sum.tokensIn ?? 0,
54
+ totalTokensOut: result._sum.tokensOut ?? 0,
55
+ totalCostCents: result._sum.costCents ?? 0,
56
+ };
57
+ }
58
+ async getOrgUsage(organizationId, period) {
59
+ const where = { organizationId };
60
+ if (period) {
61
+ where.createdAt = {
62
+ gte: period.start,
63
+ lte: period.end,
64
+ };
65
+ }
66
+ else {
67
+ const now = new Date();
68
+ where.createdAt = {
69
+ gte: new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1)),
70
+ };
71
+ }
72
+ const result = await this.db.chatMessage.aggregate({
73
+ where,
74
+ _count: { id: true },
75
+ _sum: {
76
+ tokensIn: true,
77
+ tokensOut: true,
78
+ costCents: true,
79
+ },
80
+ });
81
+ return {
82
+ totalMessages: result._count.id,
83
+ totalTokensIn: result._sum.tokensIn ?? 0,
84
+ totalTokensOut: result._sum.tokensOut ?? 0,
85
+ totalCostCents: result._sum.costCents ?? 0,
86
+ };
87
+ }
88
+ }
89
+ //# sourceMappingURL=chat-usage-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-usage-service.js","sourceRoot":"","sources":["../../src/services/chat-usage-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ import type { PrismaClient, OrgContentPolicy } from "@prisma/client";
2
+ import type { TenantContext } from "../db/tenant.js";
3
+ export interface CreatePolicyInput {
4
+ name: string;
5
+ description?: string;
6
+ category: string;
7
+ keywords: string[];
8
+ action: "block" | "warn";
9
+ enabled?: boolean;
10
+ priority?: number;
11
+ }
12
+ export interface UpdatePolicyInput {
13
+ name?: string;
14
+ description?: string;
15
+ category?: string;
16
+ keywords?: string[];
17
+ action?: "block" | "warn";
18
+ enabled?: boolean;
19
+ priority?: number;
20
+ }
21
+ export interface PolicyViolation {
22
+ policyId: string;
23
+ policyName: string;
24
+ category: string;
25
+ action: "block" | "warn";
26
+ matchedKeyword: string;
27
+ }
28
+ export interface PolicyEvaluation {
29
+ allowed: boolean;
30
+ violations: PolicyViolation[];
31
+ }
32
+ export interface LogViolationInput {
33
+ policyId: string;
34
+ policyName: string;
35
+ sessionId?: string;
36
+ userId: string;
37
+ action: string;
38
+ matchedKeyword: string;
39
+ }
40
+ export declare class ContentPolicyService {
41
+ private db;
42
+ private tenantCtx?;
43
+ constructor(db: PrismaClient, tenantCtx?: TenantContext | undefined);
44
+ list(): Promise<OrgContentPolicy[]>;
45
+ get(id: string): Promise<OrgContentPolicy | null>;
46
+ create(input: CreatePolicyInput): Promise<OrgContentPolicy>;
47
+ update(id: string, input: UpdatePolicyInput): Promise<OrgContentPolicy>;
48
+ delete(id: string): Promise<boolean>;
49
+ /**
50
+ * Evaluate content against all enabled policies.
51
+ * Returns whether the content is allowed and any violations found.
52
+ */
53
+ evaluate(content: string): Promise<PolicyEvaluation>;
54
+ logViolation(input: LogViolationInput): Promise<void>;
55
+ listLogs(options?: {
56
+ limit?: number;
57
+ offset?: number;
58
+ }): Promise<{
59
+ id: string;
60
+ createdAt: Date;
61
+ organizationId: string;
62
+ userId: string;
63
+ sessionId: string | null;
64
+ action: string;
65
+ policyId: string;
66
+ policyName: string;
67
+ matchedKeyword: string;
68
+ userOverride: boolean;
69
+ }[]>;
70
+ }
71
+ //# sourceMappingURL=content-policy-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-policy-service.d.ts","sourceRoot":"","sources":["../../src/services/content-policy-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,eAAe,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,oBAAoB;IAE7B,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,SAAS,CAAC;gBADV,EAAE,EAAE,YAAY,EAChB,SAAS,CAAC,EAAE,aAAa,YAAA;IAG7B,IAAI,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAMnC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAIjD,MAAM,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAe3D,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAgBvE,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAS1C;;;OAGG;IACG,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAgCpD,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAcrD,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;;;;;;;;;;;;CAO7D"}