@pikku/mongodb 0.12.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/CHANGELOG.md +15 -0
- package/dist/src/index.d.ts +12 -0
- package/dist/src/index.js +9 -0
- package/dist/src/mongodb-agent-run-service.d.ts +21 -0
- package/dist/src/mongodb-agent-run-service.js +127 -0
- package/dist/src/mongodb-ai-storage-service.d.ts +41 -0
- package/dist/src/mongodb-ai-storage-service.js +408 -0
- package/dist/src/mongodb-channel-store.d.ts +19 -0
- package/dist/src/mongodb-channel-store.js +52 -0
- package/dist/src/mongodb-deployment-service.d.ts +17 -0
- package/dist/src/mongodb-deployment-service.js +75 -0
- package/dist/src/mongodb-eventhub-store.d.ts +13 -0
- package/dist/src/mongodb-eventhub-store.js +37 -0
- package/dist/src/mongodb-secret-service.d.ts +30 -0
- package/dist/src/mongodb-secret-service.js +110 -0
- package/dist/src/mongodb-workflow-run-service.d.ts +36 -0
- package/dist/src/mongodb-workflow-run-service.js +146 -0
- package/dist/src/mongodb-workflow-service.d.ts +55 -0
- package/dist/src/mongodb-workflow-service.js +351 -0
- package/dist/src/pikku-mongodb.d.ts +12 -0
- package/dist/src/pikku-mongodb.js +37 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +29 -0
- package/run-tests.sh +53 -0
- package/src/index.ts +14 -0
- package/src/mongodb-agent-run-service.ts +206 -0
- package/src/mongodb-ai-storage-service.ts +576 -0
- package/src/mongodb-channel-store.ts +96 -0
- package/src/mongodb-deployment-service.ts +114 -0
- package/src/mongodb-eventhub-store.ts +52 -0
- package/src/mongodb-secret-service.ts +178 -0
- package/src/mongodb-services.test.ts +149 -0
- package/src/mongodb-workflow-run-service.ts +246 -0
- package/src/mongodb-workflow-service.ts +583 -0
- package/src/pikku-mongodb.ts +41 -0
- package/tsconfig.json +17 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# @pikku/mongodb
|
|
2
|
+
|
|
3
|
+
## 0.12.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 97bab28: Initial release of @pikku/mongodb — MongoDB-backed service implementations for Pikku, including SecretService, ChannelStore, EventHubStore, DeploymentService, WorkflowService, WorkflowRunService, AIStorageService, AIRunStateService, and AgentRunService.
|
|
8
|
+
- c7ff141: Add WorkflowVersionStatus type with draft→active lifecycle for AI-generated workflows, type all DB status fields with proper unions instead of plain strings
|
|
9
|
+
- Updated dependencies [387b2ee]
|
|
10
|
+
- Updated dependencies [32ed003]
|
|
11
|
+
- Updated dependencies [7d369f3]
|
|
12
|
+
- Updated dependencies [508a796]
|
|
13
|
+
- Updated dependencies [ffe83af]
|
|
14
|
+
- Updated dependencies [c7ff141]
|
|
15
|
+
- @pikku/core@0.12.3
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { PikkuMongoDB } from './pikku-mongodb.js';
|
|
2
|
+
export { MongoDBChannelStore } from './mongodb-channel-store.js';
|
|
3
|
+
export { MongoDBEventHubStore } from './mongodb-eventhub-store.js';
|
|
4
|
+
export { MongoDBWorkflowService } from './mongodb-workflow-service.js';
|
|
5
|
+
export { MongoDBWorkflowRunService } from './mongodb-workflow-run-service.js';
|
|
6
|
+
export { MongoDBDeploymentService } from './mongodb-deployment-service.js';
|
|
7
|
+
export { MongoDBAIStorageService } from './mongodb-ai-storage-service.js';
|
|
8
|
+
export { MongoDBAgentRunService } from './mongodb-agent-run-service.js';
|
|
9
|
+
export { MongoDBSecretService } from './mongodb-secret-service.js';
|
|
10
|
+
export type { MongoDBSecretServiceConfig } from './mongodb-secret-service.js';
|
|
11
|
+
export type { WorkflowRunService } from '@pikku/core/workflow';
|
|
12
|
+
export type { AgentRunService, AgentRunRow } from '@pikku/core/ai-agent';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { PikkuMongoDB } from './pikku-mongodb.js';
|
|
2
|
+
export { MongoDBChannelStore } from './mongodb-channel-store.js';
|
|
3
|
+
export { MongoDBEventHubStore } from './mongodb-eventhub-store.js';
|
|
4
|
+
export { MongoDBWorkflowService } from './mongodb-workflow-service.js';
|
|
5
|
+
export { MongoDBWorkflowRunService } from './mongodb-workflow-run-service.js';
|
|
6
|
+
export { MongoDBDeploymentService } from './mongodb-deployment-service.js';
|
|
7
|
+
export { MongoDBAIStorageService } from './mongodb-ai-storage-service.js';
|
|
8
|
+
export { MongoDBAgentRunService } from './mongodb-agent-run-service.js';
|
|
9
|
+
export { MongoDBSecretService } from './mongodb-secret-service.js';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AIThread, AIMessage, AgentRunRow, AgentRunService } from '@pikku/core/ai-agent';
|
|
2
|
+
import type { Db } from 'mongodb';
|
|
3
|
+
export declare class MongoDBAgentRunService implements AgentRunService {
|
|
4
|
+
private threads;
|
|
5
|
+
private messages;
|
|
6
|
+
private toolCalls;
|
|
7
|
+
private runs;
|
|
8
|
+
constructor(db: Db);
|
|
9
|
+
listThreads(options?: {
|
|
10
|
+
agentName?: string;
|
|
11
|
+
limit?: number;
|
|
12
|
+
offset?: number;
|
|
13
|
+
}): Promise<AIThread[]>;
|
|
14
|
+
getThread(threadId: string): Promise<AIThread | null>;
|
|
15
|
+
getThreadMessages(threadId: string): Promise<AIMessage[]>;
|
|
16
|
+
getThreadRuns(threadId: string): Promise<AgentRunRow[]>;
|
|
17
|
+
deleteThread(threadId: string): Promise<boolean>;
|
|
18
|
+
getDistinctAgentNames(): Promise<string[]>;
|
|
19
|
+
private mapThreadRow;
|
|
20
|
+
private mapRunRow;
|
|
21
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
export class MongoDBAgentRunService {
|
|
2
|
+
threads;
|
|
3
|
+
messages;
|
|
4
|
+
toolCalls;
|
|
5
|
+
runs;
|
|
6
|
+
constructor(db) {
|
|
7
|
+
this.threads = db.collection('ai_threads');
|
|
8
|
+
this.messages = db.collection('ai_message');
|
|
9
|
+
this.toolCalls = db.collection('ai_tool_call');
|
|
10
|
+
this.runs = db.collection('ai_run');
|
|
11
|
+
}
|
|
12
|
+
async listThreads(options) {
|
|
13
|
+
const { agentName, limit = 50, offset = 0 } = options ?? {};
|
|
14
|
+
let filter = {};
|
|
15
|
+
if (agentName) {
|
|
16
|
+
const threadIds = await this.runs.distinct('threadId', {
|
|
17
|
+
agentName,
|
|
18
|
+
});
|
|
19
|
+
filter._id = { $in: threadIds };
|
|
20
|
+
}
|
|
21
|
+
const result = await this.threads
|
|
22
|
+
.find(filter)
|
|
23
|
+
.sort({ updatedAt: -1 })
|
|
24
|
+
.skip(offset)
|
|
25
|
+
.limit(limit)
|
|
26
|
+
.toArray();
|
|
27
|
+
return result.map((row) => this.mapThreadRow(row));
|
|
28
|
+
}
|
|
29
|
+
async getThread(threadId) {
|
|
30
|
+
const row = await this.threads.findOne({ _id: threadId });
|
|
31
|
+
if (!row)
|
|
32
|
+
return null;
|
|
33
|
+
return this.mapThreadRow(row);
|
|
34
|
+
}
|
|
35
|
+
async getThreadMessages(threadId) {
|
|
36
|
+
const [msgResult, tcResult] = await Promise.all([
|
|
37
|
+
this.messages.find({ threadId }).sort({ createdAt: 1 }).toArray(),
|
|
38
|
+
this.toolCalls.find({ threadId }).sort({ createdAt: 1 }).toArray(),
|
|
39
|
+
]);
|
|
40
|
+
const tcByMessage = new Map();
|
|
41
|
+
for (const tc of tcResult) {
|
|
42
|
+
const msgId = tc.messageId;
|
|
43
|
+
if (!tcByMessage.has(msgId))
|
|
44
|
+
tcByMessage.set(msgId, []);
|
|
45
|
+
tcByMessage.get(msgId).push(tc);
|
|
46
|
+
}
|
|
47
|
+
const messages = [];
|
|
48
|
+
for (const row of msgResult) {
|
|
49
|
+
const msg = {
|
|
50
|
+
id: row._id,
|
|
51
|
+
role: row.role,
|
|
52
|
+
content: row.content ?? undefined,
|
|
53
|
+
createdAt: new Date(row.createdAt),
|
|
54
|
+
};
|
|
55
|
+
const tcs = tcByMessage.get(msg.id);
|
|
56
|
+
if (tcs?.length) {
|
|
57
|
+
msg.toolCalls = tcs.map((tc) => ({
|
|
58
|
+
id: tc._id,
|
|
59
|
+
name: tc.toolName,
|
|
60
|
+
args: tc.args,
|
|
61
|
+
}));
|
|
62
|
+
const completed = tcs.filter((tc) => tc.result != null);
|
|
63
|
+
if (completed.length) {
|
|
64
|
+
messages.push(msg);
|
|
65
|
+
messages.push({
|
|
66
|
+
id: `tool-results-${msg.id}`,
|
|
67
|
+
role: 'tool',
|
|
68
|
+
toolResults: completed.map((tc) => ({
|
|
69
|
+
id: tc._id,
|
|
70
|
+
name: tc.toolName,
|
|
71
|
+
result: tc.result,
|
|
72
|
+
})),
|
|
73
|
+
createdAt: msg.createdAt,
|
|
74
|
+
});
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
messages.push(msg);
|
|
79
|
+
}
|
|
80
|
+
return messages;
|
|
81
|
+
}
|
|
82
|
+
async getThreadRuns(threadId) {
|
|
83
|
+
const result = await this.runs
|
|
84
|
+
.find({ threadId })
|
|
85
|
+
.sort({ createdAt: -1 })
|
|
86
|
+
.toArray();
|
|
87
|
+
return result.map((row) => this.mapRunRow(row));
|
|
88
|
+
}
|
|
89
|
+
async deleteThread(threadId) {
|
|
90
|
+
await this.toolCalls.deleteMany({ threadId });
|
|
91
|
+
await this.messages.deleteMany({ threadId });
|
|
92
|
+
await this.runs.deleteMany({ threadId });
|
|
93
|
+
const result = await this.threads.deleteOne({ _id: threadId });
|
|
94
|
+
return result.deletedCount > 0;
|
|
95
|
+
}
|
|
96
|
+
async getDistinctAgentNames() {
|
|
97
|
+
const result = await this.runs.distinct('agentName');
|
|
98
|
+
return result.sort();
|
|
99
|
+
}
|
|
100
|
+
mapThreadRow(row) {
|
|
101
|
+
return {
|
|
102
|
+
id: row._id,
|
|
103
|
+
resourceId: row.resourceId,
|
|
104
|
+
title: row.title ?? undefined,
|
|
105
|
+
metadata: row.metadata ?? undefined,
|
|
106
|
+
createdAt: new Date(row.createdAt),
|
|
107
|
+
updatedAt: new Date(row.updatedAt),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
mapRunRow(row) {
|
|
111
|
+
return {
|
|
112
|
+
runId: row._id,
|
|
113
|
+
agentName: row.agentName,
|
|
114
|
+
threadId: row.threadId,
|
|
115
|
+
resourceId: row.resourceId,
|
|
116
|
+
status: row.status,
|
|
117
|
+
errorMessage: row.errorMessage ?? undefined,
|
|
118
|
+
suspendReason: row.suspendReason ?? undefined,
|
|
119
|
+
missingRpcs: row.missingRpcs ?? undefined,
|
|
120
|
+
usageInputTokens: Number(row.usageInputTokens),
|
|
121
|
+
usageOutputTokens: Number(row.usageOutputTokens),
|
|
122
|
+
usageModel: row.usageModel,
|
|
123
|
+
createdAt: new Date(row.createdAt),
|
|
124
|
+
updatedAt: new Date(row.updatedAt),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { AIStorageService, AIRunStateService, CreateRunInput } from '@pikku/core/services';
|
|
2
|
+
import type { AIThread, AIMessage, AgentRunState } from '@pikku/core/ai-agent';
|
|
3
|
+
import type { Db } from 'mongodb';
|
|
4
|
+
export declare class MongoDBAIStorageService implements AIStorageService, AIRunStateService {
|
|
5
|
+
private db;
|
|
6
|
+
private initialized;
|
|
7
|
+
private threads;
|
|
8
|
+
private aiMessages;
|
|
9
|
+
private toolCalls;
|
|
10
|
+
private workingMemory;
|
|
11
|
+
private aiRuns;
|
|
12
|
+
constructor(db: Db);
|
|
13
|
+
init(): Promise<void>;
|
|
14
|
+
createThread(resourceId: string, options?: {
|
|
15
|
+
threadId?: string;
|
|
16
|
+
title?: string;
|
|
17
|
+
metadata?: Record<string, unknown>;
|
|
18
|
+
}): Promise<AIThread>;
|
|
19
|
+
getThread(threadId: string): Promise<AIThread>;
|
|
20
|
+
getThreads(resourceId: string): Promise<AIThread[]>;
|
|
21
|
+
deleteThread(threadId: string): Promise<void>;
|
|
22
|
+
getMessages(threadId: string, options?: {
|
|
23
|
+
lastN?: number;
|
|
24
|
+
cursor?: string;
|
|
25
|
+
}): Promise<AIMessage[]>;
|
|
26
|
+
saveMessages(threadId: string, messages: AIMessage[]): Promise<void>;
|
|
27
|
+
getWorkingMemory(id: string, scope: 'resource' | 'thread'): Promise<Record<string, unknown> | null>;
|
|
28
|
+
saveWorkingMemory(id: string, scope: 'resource' | 'thread', data: Record<string, unknown>): Promise<void>;
|
|
29
|
+
createRun(run: CreateRunInput): Promise<string>;
|
|
30
|
+
updateRun(runId: string, updates: Partial<AgentRunState>): Promise<void>;
|
|
31
|
+
getRun(runId: string): Promise<AgentRunState | null>;
|
|
32
|
+
getRunsByThread(threadId: string): Promise<AgentRunState[]>;
|
|
33
|
+
private setApprovals;
|
|
34
|
+
private mapRunRow;
|
|
35
|
+
findRunByToolCallId(toolCallId: string): Promise<{
|
|
36
|
+
run: AgentRunState;
|
|
37
|
+
approval: NonNullable<AgentRunState['pendingApprovals']>[number];
|
|
38
|
+
} | null>;
|
|
39
|
+
resolveApproval(toolCallId: string, status: 'approved' | 'denied'): Promise<void>;
|
|
40
|
+
close(): Promise<void>;
|
|
41
|
+
}
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
export class MongoDBAIStorageService {
|
|
2
|
+
db;
|
|
3
|
+
initialized = false;
|
|
4
|
+
threads;
|
|
5
|
+
aiMessages;
|
|
6
|
+
toolCalls;
|
|
7
|
+
workingMemory;
|
|
8
|
+
aiRuns;
|
|
9
|
+
constructor(db) {
|
|
10
|
+
this.db = db;
|
|
11
|
+
}
|
|
12
|
+
async init() {
|
|
13
|
+
if (this.initialized)
|
|
14
|
+
return;
|
|
15
|
+
this.threads = this.db.collection('ai_threads');
|
|
16
|
+
this.aiMessages = this.db.collection('ai_message');
|
|
17
|
+
this.toolCalls = this.db.collection('ai_tool_call');
|
|
18
|
+
this.workingMemory =
|
|
19
|
+
this.db.collection('ai_working_memory');
|
|
20
|
+
this.aiRuns = this.db.collection('ai_run');
|
|
21
|
+
await this.threads.createIndex({ resourceId: 1 });
|
|
22
|
+
await this.aiMessages.createIndex({ threadId: 1, createdAt: 1 });
|
|
23
|
+
await this.toolCalls.createIndex({ threadId: 1 });
|
|
24
|
+
await this.toolCalls.createIndex({ messageId: 1 });
|
|
25
|
+
await this.workingMemory.createIndex({ id: 1, scope: 1 }, { unique: true });
|
|
26
|
+
await this.aiRuns.createIndex({ threadId: 1, createdAt: 1 });
|
|
27
|
+
this.initialized = true;
|
|
28
|
+
}
|
|
29
|
+
async createThread(resourceId, options) {
|
|
30
|
+
const id = options?.threadId ?? crypto.randomUUID();
|
|
31
|
+
const now = new Date();
|
|
32
|
+
await this.threads.insertOne({
|
|
33
|
+
_id: id,
|
|
34
|
+
resourceId,
|
|
35
|
+
title: options?.title ?? null,
|
|
36
|
+
metadata: options?.metadata ?? null,
|
|
37
|
+
createdAt: now,
|
|
38
|
+
updatedAt: now,
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
id,
|
|
42
|
+
resourceId,
|
|
43
|
+
title: options?.title,
|
|
44
|
+
metadata: options?.metadata,
|
|
45
|
+
createdAt: now,
|
|
46
|
+
updatedAt: now,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
async getThread(threadId) {
|
|
50
|
+
const row = await this.threads.findOne({ _id: threadId });
|
|
51
|
+
if (!row) {
|
|
52
|
+
throw new Error(`Thread not found: ${threadId}`);
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
id: row._id,
|
|
56
|
+
resourceId: row.resourceId,
|
|
57
|
+
title: row.title ?? undefined,
|
|
58
|
+
metadata: row.metadata ?? undefined,
|
|
59
|
+
createdAt: new Date(row.createdAt),
|
|
60
|
+
updatedAt: new Date(row.updatedAt),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
async getThreads(resourceId) {
|
|
64
|
+
const result = await this.threads
|
|
65
|
+
.find({ resourceId })
|
|
66
|
+
.sort({ updatedAt: -1 })
|
|
67
|
+
.toArray();
|
|
68
|
+
return result.map((row) => ({
|
|
69
|
+
id: row._id,
|
|
70
|
+
resourceId: row.resourceId,
|
|
71
|
+
title: row.title ?? undefined,
|
|
72
|
+
metadata: row.metadata ?? undefined,
|
|
73
|
+
createdAt: new Date(row.createdAt),
|
|
74
|
+
updatedAt: new Date(row.updatedAt),
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
async deleteThread(threadId) {
|
|
78
|
+
await this.toolCalls.deleteMany({ threadId });
|
|
79
|
+
await this.aiMessages.deleteMany({ threadId });
|
|
80
|
+
await this.aiRuns.deleteMany({ threadId });
|
|
81
|
+
await this.threads.deleteOne({ _id: threadId });
|
|
82
|
+
}
|
|
83
|
+
async getMessages(threadId, options) {
|
|
84
|
+
let filter = { threadId };
|
|
85
|
+
if (options?.cursor) {
|
|
86
|
+
const cursorRow = await this.aiMessages.findOne({ _id: options.cursor });
|
|
87
|
+
if (cursorRow) {
|
|
88
|
+
filter.createdAt = { $lt: cursorRow.createdAt };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
let msgResult;
|
|
92
|
+
if (options?.cursor || options?.lastN) {
|
|
93
|
+
const innerResult = await this.aiMessages
|
|
94
|
+
.find(filter)
|
|
95
|
+
.sort({ createdAt: -1 })
|
|
96
|
+
.limit(options?.lastN ?? 50)
|
|
97
|
+
.toArray();
|
|
98
|
+
innerResult.reverse();
|
|
99
|
+
msgResult = innerResult;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
msgResult = await this.aiMessages
|
|
103
|
+
.find(filter)
|
|
104
|
+
.sort({ createdAt: 1 })
|
|
105
|
+
.toArray();
|
|
106
|
+
}
|
|
107
|
+
const tcResult = await this.toolCalls
|
|
108
|
+
.find({ threadId })
|
|
109
|
+
.sort({ createdAt: 1 })
|
|
110
|
+
.toArray();
|
|
111
|
+
const tcByMessage = new Map();
|
|
112
|
+
for (const tc of tcResult) {
|
|
113
|
+
const msgId = tc.messageId;
|
|
114
|
+
if (!tcByMessage.has(msgId))
|
|
115
|
+
tcByMessage.set(msgId, []);
|
|
116
|
+
tcByMessage.get(msgId).push(tc);
|
|
117
|
+
}
|
|
118
|
+
const messages = [];
|
|
119
|
+
for (const row of msgResult) {
|
|
120
|
+
let parsedContent = row.content ?? undefined;
|
|
121
|
+
if (typeof row.content === 'string') {
|
|
122
|
+
try {
|
|
123
|
+
const parsed = JSON.parse(row.content);
|
|
124
|
+
if (Array.isArray(parsed)) {
|
|
125
|
+
parsedContent = parsed;
|
|
126
|
+
}
|
|
127
|
+
else if (typeof parsed === 'string') {
|
|
128
|
+
parsedContent = parsed;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// Not JSON, use raw string
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const msg = {
|
|
136
|
+
id: row._id,
|
|
137
|
+
role: row.role,
|
|
138
|
+
content: parsedContent,
|
|
139
|
+
createdAt: new Date(row.createdAt),
|
|
140
|
+
};
|
|
141
|
+
const tcs = tcByMessage.get(msg.id);
|
|
142
|
+
if (tcs?.length) {
|
|
143
|
+
msg.toolCalls = tcs.map((tc) => ({
|
|
144
|
+
id: tc._id,
|
|
145
|
+
name: tc.toolName,
|
|
146
|
+
args: tc.args,
|
|
147
|
+
}));
|
|
148
|
+
const completed = tcs.filter((tc) => tc.result != null);
|
|
149
|
+
if (completed.length) {
|
|
150
|
+
messages.push(msg);
|
|
151
|
+
messages.push({
|
|
152
|
+
id: `tool-results-${msg.id}`,
|
|
153
|
+
role: 'tool',
|
|
154
|
+
toolResults: completed.map((tc) => ({
|
|
155
|
+
id: tc._id,
|
|
156
|
+
name: tc.toolName,
|
|
157
|
+
result: tc.result,
|
|
158
|
+
})),
|
|
159
|
+
createdAt: msg.createdAt,
|
|
160
|
+
});
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
messages.push(msg);
|
|
165
|
+
}
|
|
166
|
+
return messages;
|
|
167
|
+
}
|
|
168
|
+
async saveMessages(threadId, messages) {
|
|
169
|
+
if (messages.length === 0)
|
|
170
|
+
return;
|
|
171
|
+
const toolMessages = messages.filter((m) => m.role === 'tool');
|
|
172
|
+
const nonToolMessages = messages.filter((m) => m.role !== 'tool');
|
|
173
|
+
if (nonToolMessages.length > 0) {
|
|
174
|
+
await this.aiMessages.insertMany(nonToolMessages.map((msg) => ({
|
|
175
|
+
_id: msg.id,
|
|
176
|
+
threadId,
|
|
177
|
+
role: msg.role,
|
|
178
|
+
content: msg.content != null ? JSON.stringify(msg.content) : null,
|
|
179
|
+
createdAt: msg.createdAt ?? new Date(),
|
|
180
|
+
})));
|
|
181
|
+
}
|
|
182
|
+
const toolCallDocs = nonToolMessages.flatMap((msg) => msg.toolCalls?.map((tc) => ({ ...tc, messageId: msg.id })) ?? []);
|
|
183
|
+
if (toolCallDocs.length > 0) {
|
|
184
|
+
await this.toolCalls.insertMany(toolCallDocs.map((tc) => ({
|
|
185
|
+
_id: tc.id,
|
|
186
|
+
threadId,
|
|
187
|
+
messageId: tc.messageId,
|
|
188
|
+
runId: null,
|
|
189
|
+
toolName: tc.name,
|
|
190
|
+
args: tc.args,
|
|
191
|
+
result: null,
|
|
192
|
+
approvalStatus: null,
|
|
193
|
+
approvalType: null,
|
|
194
|
+
agentRunId: null,
|
|
195
|
+
displayToolName: null,
|
|
196
|
+
displayArgs: null,
|
|
197
|
+
createdAt: new Date(),
|
|
198
|
+
})));
|
|
199
|
+
}
|
|
200
|
+
for (const toolMsg of toolMessages) {
|
|
201
|
+
if (!toolMsg.toolResults)
|
|
202
|
+
continue;
|
|
203
|
+
for (const tr of toolMsg.toolResults) {
|
|
204
|
+
await this.toolCalls.updateOne({ _id: tr.id }, { $set: { result: tr.result } });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
await this.threads.updateOne({ _id: threadId }, { $set: { updatedAt: new Date() } });
|
|
208
|
+
}
|
|
209
|
+
async getWorkingMemory(id, scope) {
|
|
210
|
+
const row = await this.workingMemory.findOne({ id, scope });
|
|
211
|
+
if (!row)
|
|
212
|
+
return null;
|
|
213
|
+
return row.data;
|
|
214
|
+
}
|
|
215
|
+
async saveWorkingMemory(id, scope, data) {
|
|
216
|
+
await this.workingMemory.updateOne({ id, scope }, {
|
|
217
|
+
$set: {
|
|
218
|
+
data,
|
|
219
|
+
updatedAt: new Date(),
|
|
220
|
+
},
|
|
221
|
+
$setOnInsert: {
|
|
222
|
+
id,
|
|
223
|
+
scope,
|
|
224
|
+
},
|
|
225
|
+
}, { upsert: true });
|
|
226
|
+
}
|
|
227
|
+
async createRun(run) {
|
|
228
|
+
const runId = crypto.randomUUID();
|
|
229
|
+
await this.aiRuns.insertOne({
|
|
230
|
+
_id: runId,
|
|
231
|
+
agentName: run.agentName,
|
|
232
|
+
threadId: run.threadId,
|
|
233
|
+
resourceId: run.resourceId,
|
|
234
|
+
status: run.status,
|
|
235
|
+
errorMessage: run.errorMessage ?? null,
|
|
236
|
+
suspendReason: run.suspendReason ?? null,
|
|
237
|
+
missingRpcs: run.missingRpcs ?? null,
|
|
238
|
+
usageInputTokens: run.usage.inputTokens,
|
|
239
|
+
usageOutputTokens: run.usage.outputTokens,
|
|
240
|
+
usageModel: run.usage.model,
|
|
241
|
+
createdAt: run.createdAt,
|
|
242
|
+
updatedAt: run.updatedAt,
|
|
243
|
+
});
|
|
244
|
+
if (run.pendingApprovals?.length) {
|
|
245
|
+
await this.setApprovals(runId, run.pendingApprovals);
|
|
246
|
+
}
|
|
247
|
+
return runId;
|
|
248
|
+
}
|
|
249
|
+
async updateRun(runId, updates) {
|
|
250
|
+
const setValues = { updatedAt: new Date() };
|
|
251
|
+
if (updates.status !== undefined) {
|
|
252
|
+
setValues.status = updates.status;
|
|
253
|
+
}
|
|
254
|
+
if (updates.errorMessage !== undefined) {
|
|
255
|
+
setValues.errorMessage = updates.errorMessage;
|
|
256
|
+
}
|
|
257
|
+
if (updates.suspendReason !== undefined) {
|
|
258
|
+
setValues.suspendReason = updates.suspendReason;
|
|
259
|
+
}
|
|
260
|
+
if (updates.missingRpcs !== undefined) {
|
|
261
|
+
setValues.missingRpcs = updates.missingRpcs;
|
|
262
|
+
}
|
|
263
|
+
if (updates.usage !== undefined) {
|
|
264
|
+
setValues.usageInputTokens = updates.usage.inputTokens;
|
|
265
|
+
setValues.usageOutputTokens = updates.usage.outputTokens;
|
|
266
|
+
setValues.usageModel = updates.usage.model;
|
|
267
|
+
}
|
|
268
|
+
await this.aiRuns.updateOne({ _id: runId }, { $set: setValues });
|
|
269
|
+
if (updates.pendingApprovals !== undefined) {
|
|
270
|
+
await this.toolCalls.updateMany({ runId, approvalStatus: { $ne: null } }, {
|
|
271
|
+
$set: {
|
|
272
|
+
approvalStatus: null,
|
|
273
|
+
runId: null,
|
|
274
|
+
approvalType: null,
|
|
275
|
+
agentRunId: null,
|
|
276
|
+
displayToolName: null,
|
|
277
|
+
displayArgs: null,
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
if (updates.pendingApprovals.length) {
|
|
281
|
+
await this.setApprovals(runId, updates.pendingApprovals);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
async getRun(runId) {
|
|
286
|
+
const row = await this.aiRuns.findOne({ _id: runId });
|
|
287
|
+
if (!row)
|
|
288
|
+
return null;
|
|
289
|
+
const approvals = await this.toolCalls
|
|
290
|
+
.find({ runId, approvalStatus: 'pending' })
|
|
291
|
+
.toArray();
|
|
292
|
+
return this.mapRunRow(row, approvals);
|
|
293
|
+
}
|
|
294
|
+
async getRunsByThread(threadId) {
|
|
295
|
+
const result = await this.aiRuns
|
|
296
|
+
.find({ threadId })
|
|
297
|
+
.sort({ createdAt: -1 })
|
|
298
|
+
.toArray();
|
|
299
|
+
const runs = [];
|
|
300
|
+
for (const row of result) {
|
|
301
|
+
const approvals = await this.toolCalls
|
|
302
|
+
.find({ runId: row._id, approvalStatus: 'pending' })
|
|
303
|
+
.toArray();
|
|
304
|
+
runs.push(this.mapRunRow(row, approvals));
|
|
305
|
+
}
|
|
306
|
+
return runs;
|
|
307
|
+
}
|
|
308
|
+
async setApprovals(runId, approvals) {
|
|
309
|
+
for (const a of approvals) {
|
|
310
|
+
if (a.type === 'agent-call') {
|
|
311
|
+
await this.toolCalls.updateOne({ _id: a.toolCallId }, {
|
|
312
|
+
$set: {
|
|
313
|
+
approvalStatus: 'pending',
|
|
314
|
+
runId,
|
|
315
|
+
approvalType: 'agent-call',
|
|
316
|
+
agentRunId: a.agentRunId,
|
|
317
|
+
displayToolName: a.displayToolName,
|
|
318
|
+
displayArgs: a.displayArgs,
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
await this.toolCalls.updateOne({ _id: a.toolCallId }, {
|
|
324
|
+
$set: {
|
|
325
|
+
approvalStatus: 'pending',
|
|
326
|
+
runId,
|
|
327
|
+
approvalType: 'tool-call',
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
mapRunRow(row, approvalRows) {
|
|
334
|
+
const pendingApprovals = approvalRows?.length
|
|
335
|
+
? approvalRows.map((a) => {
|
|
336
|
+
if (a.approvalType === 'agent-call') {
|
|
337
|
+
return {
|
|
338
|
+
type: 'agent-call',
|
|
339
|
+
toolCallId: a._id,
|
|
340
|
+
agentName: a.toolName,
|
|
341
|
+
agentRunId: a.agentRunId,
|
|
342
|
+
displayToolName: a.displayToolName,
|
|
343
|
+
displayArgs: a.displayArgs,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
return {
|
|
347
|
+
type: 'tool-call',
|
|
348
|
+
toolCallId: a._id,
|
|
349
|
+
toolName: a.toolName,
|
|
350
|
+
args: a.args,
|
|
351
|
+
};
|
|
352
|
+
})
|
|
353
|
+
: undefined;
|
|
354
|
+
return {
|
|
355
|
+
runId: row._id,
|
|
356
|
+
agentName: row.agentName,
|
|
357
|
+
threadId: row.threadId,
|
|
358
|
+
resourceId: row.resourceId,
|
|
359
|
+
status: row.status,
|
|
360
|
+
errorMessage: row.errorMessage ?? undefined,
|
|
361
|
+
suspendReason: row.suspendReason,
|
|
362
|
+
missingRpcs: row.missingRpcs ?? undefined,
|
|
363
|
+
pendingApprovals,
|
|
364
|
+
usage: {
|
|
365
|
+
inputTokens: Number(row.usageInputTokens),
|
|
366
|
+
outputTokens: Number(row.usageOutputTokens),
|
|
367
|
+
model: row.usageModel,
|
|
368
|
+
},
|
|
369
|
+
createdAt: new Date(row.createdAt),
|
|
370
|
+
updatedAt: new Date(row.updatedAt),
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
async findRunByToolCallId(toolCallId) {
|
|
374
|
+
const tc = await this.toolCalls.findOne({
|
|
375
|
+
_id: toolCallId,
|
|
376
|
+
approvalStatus: 'pending',
|
|
377
|
+
});
|
|
378
|
+
if (!tc || !tc.runId)
|
|
379
|
+
return null;
|
|
380
|
+
const run = await this.getRun(tc.runId);
|
|
381
|
+
if (!run)
|
|
382
|
+
return null;
|
|
383
|
+
let approval;
|
|
384
|
+
if (tc.approvalType === 'agent-call') {
|
|
385
|
+
approval = {
|
|
386
|
+
type: 'agent-call',
|
|
387
|
+
toolCallId: tc._id,
|
|
388
|
+
agentName: tc.toolName,
|
|
389
|
+
agentRunId: tc.agentRunId,
|
|
390
|
+
displayToolName: tc.displayToolName,
|
|
391
|
+
displayArgs: tc.displayArgs,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
approval = {
|
|
396
|
+
type: 'tool-call',
|
|
397
|
+
toolCallId: tc._id,
|
|
398
|
+
toolName: tc.toolName,
|
|
399
|
+
args: tc.args,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
return { run, approval };
|
|
403
|
+
}
|
|
404
|
+
async resolveApproval(toolCallId, status) {
|
|
405
|
+
await this.toolCalls.updateOne({ _id: toolCallId }, { $set: { approvalStatus: status } });
|
|
406
|
+
}
|
|
407
|
+
async close() { }
|
|
408
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CoreUserSession } from '@pikku/core';
|
|
2
|
+
import type { Channel } from '@pikku/core/channel';
|
|
3
|
+
import { ChannelStore } from '@pikku/core/channel';
|
|
4
|
+
import type { Db } from 'mongodb';
|
|
5
|
+
export declare class MongoDBChannelStore extends ChannelStore {
|
|
6
|
+
private db;
|
|
7
|
+
private initialized;
|
|
8
|
+
private channels;
|
|
9
|
+
private subscriptions;
|
|
10
|
+
constructor(db: Db);
|
|
11
|
+
init(): Promise<void>;
|
|
12
|
+
addChannel({ channelId, channelName, openingData, }: Channel): Promise<void>;
|
|
13
|
+
removeChannels(channelIds: string[]): Promise<void>;
|
|
14
|
+
setUserSession(channelId: string, session: CoreUserSession | null): Promise<void>;
|
|
15
|
+
getChannelAndSession(channelId: string): Promise<Channel & {
|
|
16
|
+
session: CoreUserSession;
|
|
17
|
+
}>;
|
|
18
|
+
close(): Promise<void>;
|
|
19
|
+
}
|