@apart-tech/intelligence-core 1.17.3 → 1.19.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.
- package/dist/auth/ability.d.ts +1 -1
- package/dist/auth/ability.d.ts.map +1 -1
- package/dist/auth/ability.js +19 -0
- package/dist/auth/ability.js.map +1 -1
- package/dist/auth/ability.test.js +58 -4
- package/dist/auth/ability.test.js.map +1 -1
- package/dist/db/tenant.d.ts.map +1 -1
- package/dist/db/tenant.js +10 -0
- package/dist/db/tenant.js.map +1 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/services/__tests__/agent-instruction-service.test.d.ts +2 -0
- package/dist/services/__tests__/agent-instruction-service.test.d.ts.map +1 -0
- package/dist/services/__tests__/agent-instruction-service.test.js +408 -0
- package/dist/services/__tests__/agent-instruction-service.test.js.map +1 -0
- package/dist/services/agent-instruction-service.d.ts +82 -0
- package/dist/services/agent-instruction-service.d.ts.map +1 -0
- package/dist/services/agent-instruction-service.js +249 -0
- package/dist/services/agent-instruction-service.js.map +1 -0
- package/dist/services/agent-run-service.d.ts +61 -0
- package/dist/services/agent-run-service.d.ts.map +1 -1
- package/dist/services/agent-run-service.js +72 -0
- package/dist/services/agent-run-service.js.map +1 -1
- package/dist/services/chat-context-service.d.ts +33 -0
- package/dist/services/chat-context-service.d.ts.map +1 -0
- package/dist/services/chat-context-service.js +110 -0
- package/dist/services/chat-context-service.js.map +1 -0
- package/dist/services/chat-service.d.ts +83 -0
- package/dist/services/chat-service.d.ts.map +1 -0
- package/dist/services/chat-service.js +103 -0
- package/dist/services/chat-service.js.map +1 -0
- package/dist/services/chat-usage-service.d.ts +36 -0
- package/dist/services/chat-usage-service.d.ts.map +1 -0
- package/dist/services/chat-usage-service.js +89 -0
- package/dist/services/chat-usage-service.js.map +1 -0
- package/dist/services/llm-router-service.d.ts +29 -0
- package/dist/services/llm-router-service.d.ts.map +1 -0
- package/dist/services/llm-router-service.js +70 -0
- package/dist/services/llm-router-service.js.map +1 -0
- package/dist/services/org-llm-provider-service.d.ts +47 -0
- package/dist/services/org-llm-provider-service.d.ts.map +1 -0
- package/dist/services/org-llm-provider-service.js +126 -0
- package/dist/services/org-llm-provider-service.js.map +1 -0
- package/package.json +1 -1
- package/prisma/schema.prisma +317 -162
|
@@ -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":"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"}
|
|
@@ -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 ?? "private",
|
|
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,29 @@
|
|
|
1
|
+
import type { OrgLlmProviderService } from "./org-llm-provider-service.js";
|
|
2
|
+
export interface RoutingCriteria {
|
|
3
|
+
preferredModel?: string;
|
|
4
|
+
preferredProvider?: string;
|
|
5
|
+
dataResidency?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface RoutingDecision {
|
|
8
|
+
provider: string;
|
|
9
|
+
model: string;
|
|
10
|
+
apiKey: string;
|
|
11
|
+
baseUrl: string | null;
|
|
12
|
+
providerId: string;
|
|
13
|
+
}
|
|
14
|
+
export declare class LlmRouterService {
|
|
15
|
+
private providerService;
|
|
16
|
+
constructor(providerService: OrgLlmProviderService);
|
|
17
|
+
/**
|
|
18
|
+
* Route to the best available LLM provider/model based on criteria.
|
|
19
|
+
*
|
|
20
|
+
* Logic:
|
|
21
|
+
* 1. If preferredModel + preferredProvider specified and available → use it
|
|
22
|
+
* 2. If preferredModel specified → find first enabled provider with that model
|
|
23
|
+
* 3. Otherwise pick highest-priority enabled provider
|
|
24
|
+
* 4. Respect data residency constraints
|
|
25
|
+
*/
|
|
26
|
+
route(organizationId: string, criteria?: RoutingCriteria): Promise<RoutingDecision | null>;
|
|
27
|
+
private resolveDecision;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=llm-router-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm-router-service.d.ts","sourceRoot":"","sources":["../../src/services/llm-router-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAyB,MAAM,+BAA+B,CAAC;AAElG,MAAM,WAAW,eAAe;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,gBAAgB;IACf,OAAO,CAAC,eAAe;gBAAf,eAAe,EAAE,qBAAqB;IAE1D;;;;;;;;OAQG;IACG,KAAK,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,GAAE,eAAoB,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;YAoDtF,eAAe;CAgB9B"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export class LlmRouterService {
|
|
2
|
+
providerService;
|
|
3
|
+
constructor(providerService) {
|
|
4
|
+
this.providerService = providerService;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Route to the best available LLM provider/model based on criteria.
|
|
8
|
+
*
|
|
9
|
+
* Logic:
|
|
10
|
+
* 1. If preferredModel + preferredProvider specified and available → use it
|
|
11
|
+
* 2. If preferredModel specified → find first enabled provider with that model
|
|
12
|
+
* 3. Otherwise pick highest-priority enabled provider
|
|
13
|
+
* 4. Respect data residency constraints
|
|
14
|
+
*/
|
|
15
|
+
async route(organizationId, criteria = {}) {
|
|
16
|
+
const providers = await this.providerService.list(organizationId);
|
|
17
|
+
const enabledProviders = providers.filter((p) => p.enabled);
|
|
18
|
+
if (enabledProviders.length === 0)
|
|
19
|
+
return null;
|
|
20
|
+
// Filter by data residency
|
|
21
|
+
const residencyFiltered = criteria.dataResidency
|
|
22
|
+
? enabledProviders.filter((p) => p.dataResidency === "any" || p.dataResidency === criteria.dataResidency)
|
|
23
|
+
: enabledProviders;
|
|
24
|
+
if (residencyFiltered.length === 0)
|
|
25
|
+
return null;
|
|
26
|
+
// If preferred provider + model specified
|
|
27
|
+
if (criteria.preferredProvider && criteria.preferredModel) {
|
|
28
|
+
const match = residencyFiltered.find((p) => p.provider === criteria.preferredProvider && p.models.includes(criteria.preferredModel));
|
|
29
|
+
if (match) {
|
|
30
|
+
return this.resolveDecision(organizationId, match.provider, criteria.preferredModel);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// If preferred model specified — find first provider that has it
|
|
34
|
+
if (criteria.preferredModel) {
|
|
35
|
+
const match = residencyFiltered.find((p) => p.models.includes(criteria.preferredModel));
|
|
36
|
+
if (match) {
|
|
37
|
+
return this.resolveDecision(organizationId, match.provider, criteria.preferredModel);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// If preferred provider specified — use its default model
|
|
41
|
+
if (criteria.preferredProvider) {
|
|
42
|
+
const match = residencyFiltered.find((p) => p.provider === criteria.preferredProvider);
|
|
43
|
+
if (match) {
|
|
44
|
+
const model = match.defaultModel ?? match.models[0];
|
|
45
|
+
if (model) {
|
|
46
|
+
return this.resolveDecision(organizationId, match.provider, model);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Fall back to highest priority provider's default model
|
|
51
|
+
const best = residencyFiltered[0]; // already sorted by priority desc
|
|
52
|
+
const model = best.defaultModel ?? best.models[0];
|
|
53
|
+
if (!model)
|
|
54
|
+
return null;
|
|
55
|
+
return this.resolveDecision(organizationId, best.provider, model);
|
|
56
|
+
}
|
|
57
|
+
async resolveDecision(organizationId, provider, model) {
|
|
58
|
+
const withKey = await this.providerService.getWithKey(organizationId, provider);
|
|
59
|
+
if (!withKey)
|
|
60
|
+
return null;
|
|
61
|
+
return {
|
|
62
|
+
provider: withKey.provider,
|
|
63
|
+
model,
|
|
64
|
+
apiKey: withKey.apiKey,
|
|
65
|
+
baseUrl: withKey.baseUrl,
|
|
66
|
+
providerId: withKey.id,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=llm-router-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm-router-service.js","sourceRoot":"","sources":["../../src/services/llm-router-service.ts"],"names":[],"mappings":"AAgBA,MAAM,OAAO,gBAAgB;IACP;IAApB,YAAoB,eAAsC;QAAtC,oBAAe,GAAf,eAAe,CAAuB;IAAG,CAAC;IAE9D;;;;;;;;OAQG;IACH,KAAK,CAAC,KAAK,CAAC,cAAsB,EAAE,WAA4B,EAAE;QAChE,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAClE,MAAM,gBAAgB,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAE5D,IAAI,gBAAgB,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAE/C,2BAA2B;QAC3B,MAAM,iBAAiB,GAAG,QAAQ,CAAC,aAAa;YAC9C,CAAC,CAAC,gBAAgB,CAAC,MAAM,CACrB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,KAAK,KAAK,IAAI,CAAC,CAAC,aAAa,KAAK,QAAQ,CAAC,aAAa,CAC/E;YACH,CAAC,CAAC,gBAAgB,CAAC;QAErB,IAAI,iBAAiB,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAEhD,0CAA0C;QAC1C,IAAI,QAAQ,CAAC,iBAAiB,IAAI,QAAQ,CAAC,cAAc,EAAE,CAAC;YAC1D,MAAM,KAAK,GAAG,iBAAiB,CAAC,IAAI,CAClC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,iBAAiB,IAAI,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,cAAe,CAAC,CAChG,CAAC;YACF,IAAI,KAAK,EAAE,CAAC;gBACV,OAAO,IAAI,CAAC,eAAe,CAAC,cAAc,EAAE,KAAK,CAAC,QAAQ,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC;YACvF,CAAC;QACH,CAAC;QAED,iEAAiE;QACjE,IAAI,QAAQ,CAAC,cAAc,EAAE,CAAC;YAC5B,MAAM,KAAK,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,cAAe,CAAC,CAAC,CAAC;YACzF,IAAI,KAAK,EAAE,CAAC;gBACV,OAAO,IAAI,CAAC,eAAe,CAAC,cAAc,EAAE,KAAK,CAAC,QAAQ,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC;YACvF,CAAC;QACH,CAAC;QAED,0DAA0D;QAC1D,IAAI,QAAQ,CAAC,iBAAiB,EAAE,CAAC;YAC/B,MAAM,KAAK,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,iBAAiB,CAAC,CAAC;YACvF,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,KAAK,GAAG,KAAK,CAAC,YAAY,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBACpD,IAAI,KAAK,EAAE,CAAC;oBACV,OAAO,IAAI,CAAC,eAAe,CAAC,cAAc,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;gBACrE,CAAC;YACH,CAAC;QACH,CAAC;QAED,yDAAyD;QACzD,MAAM,IAAI,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC,kCAAkC;QACrE,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAClD,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QAExB,OAAO,IAAI,CAAC,eAAe,CAAC,cAAc,EAAE,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IACpE,CAAC;IAEO,KAAK,CAAC,eAAe,CAC3B,cAAsB,EACtB,QAAgB,EAChB,KAAa;QAEb,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;QAChF,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAE1B,OAAO;YACL,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,KAAK;YACL,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,UAAU,EAAE,OAAO,CAAC,EAAE;SACvB,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { PrismaClient } from "@prisma/client";
|
|
2
|
+
export type LlmProviderName = "openai" | "anthropic" | "google" | "mistral" | "ollama";
|
|
3
|
+
export interface OrgLlmProviderRecord {
|
|
4
|
+
id: string;
|
|
5
|
+
provider: string;
|
|
6
|
+
displayName: string;
|
|
7
|
+
baseUrl: string | null;
|
|
8
|
+
models: string[];
|
|
9
|
+
defaultModel: string | null;
|
|
10
|
+
dataResidency: string;
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
priority: number;
|
|
13
|
+
maxTokensPerMin: number | null;
|
|
14
|
+
createdAt: Date;
|
|
15
|
+
updatedAt: Date;
|
|
16
|
+
}
|
|
17
|
+
export interface OrgLlmProviderWithKey extends OrgLlmProviderRecord {
|
|
18
|
+
apiKey: string;
|
|
19
|
+
}
|
|
20
|
+
export interface SetOrgLlmProviderInput {
|
|
21
|
+
provider: string;
|
|
22
|
+
displayName: string;
|
|
23
|
+
apiKey: string;
|
|
24
|
+
baseUrl?: string;
|
|
25
|
+
models: string[];
|
|
26
|
+
defaultModel?: string;
|
|
27
|
+
dataResidency?: string;
|
|
28
|
+
enabled?: boolean;
|
|
29
|
+
priority?: number;
|
|
30
|
+
maxTokensPerMin?: number;
|
|
31
|
+
}
|
|
32
|
+
export declare class OrgLlmProviderService {
|
|
33
|
+
private db;
|
|
34
|
+
constructor(db: PrismaClient);
|
|
35
|
+
set(organizationId: string, input: SetOrgLlmProviderInput): Promise<OrgLlmProviderRecord>;
|
|
36
|
+
get(organizationId: string, provider: string): Promise<OrgLlmProviderRecord | null>;
|
|
37
|
+
getWithKey(organizationId: string, provider: string): Promise<OrgLlmProviderWithKey | null>;
|
|
38
|
+
list(organizationId: string): Promise<OrgLlmProviderRecord[]>;
|
|
39
|
+
delete(organizationId: string, provider: string): Promise<boolean>;
|
|
40
|
+
listAvailableModels(organizationId: string): Promise<{
|
|
41
|
+
provider: string;
|
|
42
|
+
models: string[];
|
|
43
|
+
defaultModel: string | null;
|
|
44
|
+
}[]>;
|
|
45
|
+
private toRecord;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=org-llm-provider-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"org-llm-provider-service.d.ts","sourceRoot":"","sources":["../../src/services/org-llm-provider-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAYnD,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEvF,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,qBAAsB,SAAQ,oBAAoB;IACjE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,qBAAa,qBAAqB;IACpB,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,YAAY;IAE9B,GAAG,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAwCzF,GAAG,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAYnF,UAAU,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC;IAiB3F,IAAI,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAQ7D,MAAM,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBlE,mBAAmB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,EAAE,CAAC;IASjI,OAAO,CAAC,QAAQ;CA6BjB"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { encryptAesGcm, decryptAesGcm } from "../lib/crypto.js";
|
|
2
|
+
import { getAgentKeySecret } from "../lib/encryption-keys.js";
|
|
3
|
+
function encrypt(plaintext) {
|
|
4
|
+
return encryptAesGcm(plaintext, getAgentKeySecret());
|
|
5
|
+
}
|
|
6
|
+
function decrypt(encoded) {
|
|
7
|
+
return decryptAesGcm(encoded, getAgentKeySecret());
|
|
8
|
+
}
|
|
9
|
+
export class OrgLlmProviderService {
|
|
10
|
+
db;
|
|
11
|
+
constructor(db) {
|
|
12
|
+
this.db = db;
|
|
13
|
+
}
|
|
14
|
+
async set(organizationId, input) {
|
|
15
|
+
const encryptedKey = encrypt(input.apiKey);
|
|
16
|
+
const record = await this.db.orgLlmProvider.upsert({
|
|
17
|
+
where: {
|
|
18
|
+
organizationId_provider: {
|
|
19
|
+
organizationId,
|
|
20
|
+
provider: input.provider,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
create: {
|
|
24
|
+
organizationId,
|
|
25
|
+
provider: input.provider,
|
|
26
|
+
displayName: input.displayName,
|
|
27
|
+
encryptedApiKey: encryptedKey,
|
|
28
|
+
baseUrl: input.baseUrl ?? null,
|
|
29
|
+
models: input.models,
|
|
30
|
+
defaultModel: input.defaultModel ?? null,
|
|
31
|
+
dataResidency: input.dataResidency ?? "any",
|
|
32
|
+
enabled: input.enabled ?? true,
|
|
33
|
+
priority: input.priority ?? 0,
|
|
34
|
+
maxTokensPerMin: input.maxTokensPerMin ?? null,
|
|
35
|
+
},
|
|
36
|
+
update: {
|
|
37
|
+
displayName: input.displayName,
|
|
38
|
+
encryptedApiKey: encryptedKey,
|
|
39
|
+
baseUrl: input.baseUrl ?? null,
|
|
40
|
+
models: input.models,
|
|
41
|
+
defaultModel: input.defaultModel ?? undefined,
|
|
42
|
+
dataResidency: input.dataResidency ?? undefined,
|
|
43
|
+
enabled: input.enabled ?? undefined,
|
|
44
|
+
priority: input.priority ?? undefined,
|
|
45
|
+
maxTokensPerMin: input.maxTokensPerMin ?? undefined,
|
|
46
|
+
updatedAt: new Date(),
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
return this.toRecord(record);
|
|
50
|
+
}
|
|
51
|
+
async get(organizationId, provider) {
|
|
52
|
+
const record = await this.db.orgLlmProvider.findUnique({
|
|
53
|
+
where: {
|
|
54
|
+
organizationId_provider: {
|
|
55
|
+
organizationId,
|
|
56
|
+
provider,
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
return record ? this.toRecord(record) : null;
|
|
61
|
+
}
|
|
62
|
+
async getWithKey(organizationId, provider) {
|
|
63
|
+
const record = await this.db.orgLlmProvider.findUnique({
|
|
64
|
+
where: {
|
|
65
|
+
organizationId_provider: {
|
|
66
|
+
organizationId,
|
|
67
|
+
provider,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
if (!record)
|
|
72
|
+
return null;
|
|
73
|
+
return {
|
|
74
|
+
...this.toRecord(record),
|
|
75
|
+
apiKey: decrypt(record.encryptedApiKey),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async list(organizationId) {
|
|
79
|
+
const records = await this.db.orgLlmProvider.findMany({
|
|
80
|
+
where: { organizationId },
|
|
81
|
+
orderBy: { priority: "desc" },
|
|
82
|
+
});
|
|
83
|
+
return records.map((r) => this.toRecord(r));
|
|
84
|
+
}
|
|
85
|
+
async delete(organizationId, provider) {
|
|
86
|
+
try {
|
|
87
|
+
await this.db.orgLlmProvider.delete({
|
|
88
|
+
where: {
|
|
89
|
+
organizationId_provider: {
|
|
90
|
+
organizationId,
|
|
91
|
+
provider,
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async listAvailableModels(organizationId) {
|
|
102
|
+
const records = await this.db.orgLlmProvider.findMany({
|
|
103
|
+
where: { organizationId, enabled: true },
|
|
104
|
+
orderBy: { priority: "desc" },
|
|
105
|
+
select: { provider: true, models: true, defaultModel: true },
|
|
106
|
+
});
|
|
107
|
+
return records;
|
|
108
|
+
}
|
|
109
|
+
toRecord(row) {
|
|
110
|
+
return {
|
|
111
|
+
id: row.id,
|
|
112
|
+
provider: row.provider,
|
|
113
|
+
displayName: row.displayName,
|
|
114
|
+
baseUrl: row.baseUrl,
|
|
115
|
+
models: row.models,
|
|
116
|
+
defaultModel: row.defaultModel,
|
|
117
|
+
dataResidency: row.dataResidency,
|
|
118
|
+
enabled: row.enabled,
|
|
119
|
+
priority: row.priority,
|
|
120
|
+
maxTokensPerMin: row.maxTokensPerMin,
|
|
121
|
+
createdAt: row.createdAt,
|
|
122
|
+
updatedAt: row.updatedAt,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=org-llm-provider-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"org-llm-provider-service.js","sourceRoot":"","sources":["../../src/services/org-llm-provider-service.ts"],"names":[],"mappings":"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"}
|