@pikku/mongodb 0.12.15 → 0.12.18

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 CHANGED
@@ -1,5 +1,111 @@
1
1
  # @pikku/mongodb
2
2
 
3
+ ## 0.12.18
4
+
5
+ ### Patch Changes
6
+
7
+ - 7406bfe: Rename the agent runtime from `AI*` to `Agent*` (#596)
8
+
9
+ `AI` described the model provider, not the thing being named. Every symbol that
10
+ belongs to the agent runtime now says `Agent`; the symbols that genuinely wrap a
11
+ model provider — `AIEmbeddingService`, `AIProviderOptions`, `AIEmbedParams`,
12
+ `AITranscriptionParams`, `AIGenerateImageParams` and their siblings, and the
13
+ `@pikku/ai-vercel` / `@pikku/ai-deepinfra` / `@pikku/ai-voice` packages — keep
14
+ their names.
15
+
16
+ **Wiring**
17
+ - `pikkuAIAgent` → `pikkuAgent`, `pikkuAIScorer` → `pikkuAgentScorer`,
18
+ `pikkuAIJudge` → `pikkuAgentJudge`
19
+ - `CoreAIAgent` → `CoreAgent`, `AIAgentInput` → `AgentInput`, `AIAgentStep` →
20
+ `AgentStep`, `AIMessage` → `AgentMessage`, and the rest of the agent types
21
+ - `AIAgentRunnerService` → `AgentRunnerService`, `AIStorageService` →
22
+ `AgentStorageService`, `AIRunStateService` → `AgentRunStateService`
23
+
24
+ **Entry points**
25
+
26
+ `@pikku/core/agent` → `@pikku/core/agent`, `@pikku/core/agent-scorer` →
27
+ `@pikku/core/agent-scorer`.
28
+
29
+ **Queues**
30
+
31
+ The scorer queues are now `agent-score-fast` and `agent-score-slow`. Drain the
32
+ old `ai-score-fast` / `ai-score-slow` queues before deploying — jobs still
33
+ sitting on them when the new workers start will never be picked up.
34
+
35
+ **Scaffolds**
36
+
37
+ The agent scaffold pikku wrote for your project — `<scaffold>/agent/agent.gen.ts`
38
+ and its schemas file — imports `@pikku/core/ai-agent`, which no longer exists. A
39
+ scaffold is normally written once and then left alone, so `pikku all` would find
40
+ it present and leave the broken import in place. It now deletes an agent scaffold
41
+ importing either removed entry point and regenerates it in the same run. Anything
42
+ you added to that file goes with it, so move local edits out first.
43
+
44
+ **Database**
45
+
46
+ The agent tables are renamed: `ai_threads`, `ai_message`, `ai_tool_call`,
47
+ `ai_working_memory`, `ai_run` and `ai_run_score` become `agent_threads`,
48
+ `agent_message`, `agent_tool_call`, `agent_working_memory`, `agent_run` and
49
+ `agent_run_score`, along with their indexes and the `ai_working_memory_pk`
50
+ constraint. The same rename applies to the MongoDB collections.
51
+
52
+ `ensurePikkuSchema` creates tables it cannot find, so an existing database will
53
+ get empty `agent_*` tables and leave the old data stranded in `ai_*`. Rename
54
+ them before the first boot on the new version:
55
+
56
+ ```sql
57
+ ALTER TABLE ai_threads RENAME TO agent_threads;
58
+ ALTER TABLE ai_message RENAME TO agent_message;
59
+ ALTER TABLE ai_tool_call RENAME TO agent_tool_call;
60
+ ALTER TABLE ai_working_memory RENAME TO agent_working_memory;
61
+ ALTER TABLE ai_run RENAME TO agent_run;
62
+ ALTER TABLE ai_run_score RENAME TO agent_run_score;
63
+ ```
64
+
65
+ - 6794681: Publish the ecosystem surface as per-area sub-barrels under `@pikku/core/ecosystem/*`, and point generated code, the CLI, the inspector and the runtime adapters at them.
66
+
67
+ 348 names that only generated code, the toolchain or a runtime adapter ever imports now have a second home on `@pikku/core/ecosystem/<area>` — one sub-barrel per area, matching how core already publishes its entrypoints, so no single barrel grows without bound and a consumer only pulls in the area it uses.
68
+
69
+ This step is additive: every name is still exported from the entrypoint it was published from before, so nothing downstream breaks. Removing them from the app-facing barrels is a later change, and needs a release carrying `./ecosystem/*` first.
70
+
71
+ - Updated dependencies [7406bfe]
72
+ - Updated dependencies [6794681]
73
+ - @pikku/core@0.12.84
74
+
75
+ ## 0.12.17
76
+
77
+ ### Patch Changes
78
+
79
+ - fd9d834: Stop publishing internals that only their own package or file used. The declarations stay; only the entrypoint re-export is removed, so nothing that imported a name from where it is declared is affected.
80
+ - Updated dependencies [063f43a]
81
+ - Updated dependencies [ce66bf8]
82
+ - Updated dependencies [d0307a8]
83
+ - Updated dependencies [ce66bf8]
84
+ - Updated dependencies [3ad2131]
85
+ - Updated dependencies [b930dca]
86
+ - Updated dependencies [b95e77d]
87
+ - Updated dependencies [fd9d834]
88
+ - Updated dependencies [8978fbd]
89
+ - @pikku/core@0.12.82
90
+
91
+ ## 0.12.16
92
+
93
+ ### Patch Changes
94
+
95
+ - e110c55: Add runtime scoring for AI agents: `pikkuAIScorer` for heuristic grades and
96
+ `pikkuAIJudge` for LLM-judged ones, graded off the request path on two queue
97
+ lanes so a slow judge cannot starve the cheap checks. Grades are sampled
98
+ deterministically per `(run, scorer)` and persisted to `ai_run_score`.
99
+ - Updated dependencies [e110c55]
100
+ - Updated dependencies [e110c55]
101
+ - Updated dependencies [e110c55]
102
+ - Updated dependencies [acc8077]
103
+ - Updated dependencies [905f737]
104
+ - Updated dependencies [3cc6428]
105
+ - Updated dependencies [c524adf]
106
+ - Updated dependencies [e110c55]
107
+ - @pikku/core@0.12.81
108
+
3
109
  ## 0.12.15
4
110
 
5
111
  ### Patch Changes
@@ -4,10 +4,9 @@ export { MongoDBEventHubStore } from './mongodb-eventhub-store.js';
4
4
  export { MongoDBWorkflowService } from './mongodb-workflow-service.js';
5
5
  export { MongoDBWorkflowRunService } from './mongodb-workflow-run-service.js';
6
6
  export { MongoDBDeploymentService } from './mongodb-deployment-service.js';
7
- export { MongoDBAIStorageService } from './mongodb-ai-storage-service.js';
7
+ export { MongoDBAgentStorageService } from './mongodb-agent-storage-service.js';
8
8
  export { MongoDBAgentRunService } from './mongodb-agent-run-service.js';
9
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';
10
+ export type { WorkflowRunService } from '@pikku/core/ecosystem/workflow';
11
+ export type { AgentRunService, AgentRunRow, } from '@pikku/core/ecosystem/agent';
13
12
  export { MongoDBSessionStore } from './mongodb-session-store.js';
package/dist/src/index.js CHANGED
@@ -4,7 +4,7 @@ export { MongoDBEventHubStore } from './mongodb-eventhub-store.js';
4
4
  export { MongoDBWorkflowService } from './mongodb-workflow-service.js';
5
5
  export { MongoDBWorkflowRunService } from './mongodb-workflow-run-service.js';
6
6
  export { MongoDBDeploymentService } from './mongodb-deployment-service.js';
7
- export { MongoDBAIStorageService } from './mongodb-ai-storage-service.js';
7
+ export { MongoDBAgentStorageService } from './mongodb-agent-storage-service.js';
8
8
  export { MongoDBAgentRunService } from './mongodb-agent-run-service.js';
9
9
  export { MongoDBSecretService } from './mongodb-secret-service.js';
10
10
  export { MongoDBSessionStore } from './mongodb-session-store.js';
@@ -1,4 +1,5 @@
1
- import type { AIThread, AIMessage, AgentRunRow, AgentRunService } from '@pikku/core/ai-agent';
1
+ import type { AgentThread, AgentMessage } from '@pikku/core/agent';
2
+ import type { AgentRunRow, AgentRunService } from '@pikku/core/ecosystem/agent';
2
3
  import type { Db } from 'mongodb';
3
4
  export declare class MongoDBAgentRunService implements AgentRunService {
4
5
  private threads;
@@ -12,9 +13,9 @@ export declare class MongoDBAgentRunService implements AgentRunService {
12
13
  owners?: string[];
13
14
  limit?: number;
14
15
  offset?: number;
15
- }): Promise<AIThread[]>;
16
- getThread(threadId: string): Promise<AIThread | null>;
17
- getThreadMessages(threadId: string): Promise<AIMessage[]>;
16
+ }): Promise<AgentThread[]>;
17
+ getThread(threadId: string): Promise<AgentThread | null>;
18
+ getThreadMessages(threadId: string): Promise<AgentMessage[]>;
18
19
  getThreadRuns(threadId: string): Promise<AgentRunRow[]>;
19
20
  deleteThread(threadId: string): Promise<boolean>;
20
21
  getDistinctAgentNames(): Promise<string[]>;
@@ -7,10 +7,10 @@ export class MongoDBAgentRunService {
7
7
  toolCalls;
8
8
  runs;
9
9
  constructor(db) {
10
- this.threads = db.collection('ai_threads');
11
- this.messages = db.collection('ai_message');
12
- this.toolCalls = db.collection('ai_tool_call');
13
- this.runs = db.collection('ai_run');
10
+ this.threads = db.collection('agent_threads');
11
+ this.messages = db.collection('agent_message');
12
+ this.toolCalls = db.collection('agent_tool_call');
13
+ this.runs = db.collection('agent_run');
14
14
  }
15
15
  async listThreads(options) {
16
16
  const { agentName, resourceId, owners, limit = 50, offset = 0, } = options ?? {};
@@ -0,0 +1,45 @@
1
+ import type { AgentStorageService, AgentRunStateService, CreateRunInput, SaveScoreInput } from '@pikku/core/services';
2
+ import type { AgentThread, AgentMessage, AgentRunState } from '@pikku/core/agent';
3
+ import type { AgentRunScore } from '@pikku/core/agent-scorer';
4
+ import type { Db } from 'mongodb';
5
+ export declare class MongoDBAgentStorageService implements AgentStorageService, AgentRunStateService {
6
+ private db;
7
+ private initialized;
8
+ private threads;
9
+ private agentMessages;
10
+ private toolCalls;
11
+ private workingMemory;
12
+ private agentRuns;
13
+ private agentRunScores;
14
+ constructor(db: Db);
15
+ init(): Promise<void>;
16
+ createThread(resourceId: string, options?: {
17
+ threadId?: string;
18
+ title?: string;
19
+ metadata?: Record<string, unknown>;
20
+ }): Promise<AgentThread>;
21
+ getThread(threadId: string): Promise<AgentThread>;
22
+ getThreads(resourceId: string): Promise<AgentThread[]>;
23
+ deleteThread(threadId: string): Promise<void>;
24
+ getMessages(threadId: string, options?: {
25
+ lastN?: number;
26
+ cursor?: string;
27
+ }): Promise<AgentMessage[]>;
28
+ saveMessages(threadId: string, messages: AgentMessage[]): Promise<void>;
29
+ getWorkingMemory(id: string, scope: 'resource' | 'thread'): Promise<Record<string, unknown> | null>;
30
+ saveWorkingMemory(id: string, scope: 'resource' | 'thread', data: Record<string, unknown>): Promise<void>;
31
+ createRun(run: CreateRunInput): Promise<string>;
32
+ updateRun(runId: string, updates: Partial<AgentRunState>): Promise<void>;
33
+ getRun(runId: string): Promise<AgentRunState | null>;
34
+ getRunsByThread(threadId: string): Promise<AgentRunState[]>;
35
+ private setApprovals;
36
+ private mapRunRow;
37
+ findRunByToolCallId(toolCallId: string): Promise<{
38
+ run: AgentRunState;
39
+ approval: NonNullable<AgentRunState['pendingApprovals']>[number];
40
+ } | null>;
41
+ resolveApproval(toolCallId: string, status: 'approved' | 'denied'): Promise<boolean>;
42
+ saveScore(score: SaveScoreInput): Promise<void>;
43
+ getScores(runId: string): Promise<AgentRunScore[]>;
44
+ close(): Promise<void>;
45
+ }
@@ -0,0 +1,440 @@
1
+ export class MongoDBAgentStorageService {
2
+ db;
3
+ initialized = false;
4
+ threads;
5
+ agentMessages;
6
+ toolCalls;
7
+ workingMemory;
8
+ agentRuns;
9
+ agentRunScores;
10
+ constructor(db) {
11
+ this.db = db;
12
+ }
13
+ async init() {
14
+ if (this.initialized)
15
+ return;
16
+ this.threads = this.db.collection('agent_threads');
17
+ this.agentMessages = this.db.collection('agent_message');
18
+ this.toolCalls = this.db.collection('agent_tool_call');
19
+ this.workingMemory = this.db.collection('agent_working_memory');
20
+ this.agentRuns = this.db.collection('agent_run');
21
+ this.agentRunScores =
22
+ this.db.collection('agent_run_score');
23
+ await this.threads.createIndex({ resourceId: 1 });
24
+ await this.agentMessages.createIndex({ threadId: 1, createdAt: 1 });
25
+ await this.toolCalls.createIndex({ threadId: 1 });
26
+ await this.toolCalls.createIndex({ messageId: 1 });
27
+ await this.workingMemory.createIndex({ id: 1, scope: 1 }, { unique: true });
28
+ await this.agentRuns.createIndex({ threadId: 1, createdAt: 1 });
29
+ await this.agentRunScores.createIndex({ runId: 1, createdAt: 1 });
30
+ this.initialized = true;
31
+ }
32
+ async createThread(resourceId, options) {
33
+ const id = options?.threadId ?? crypto.randomUUID();
34
+ const now = new Date();
35
+ await this.threads.insertOne({
36
+ _id: id,
37
+ resourceId,
38
+ title: options?.title ?? null,
39
+ metadata: options?.metadata ?? null,
40
+ createdAt: now,
41
+ updatedAt: now,
42
+ });
43
+ return {
44
+ id,
45
+ resourceId,
46
+ title: options?.title,
47
+ metadata: options?.metadata,
48
+ createdAt: now,
49
+ updatedAt: now,
50
+ };
51
+ }
52
+ async getThread(threadId) {
53
+ const row = await this.threads.findOne({ _id: threadId });
54
+ if (!row) {
55
+ throw new Error(`Thread not found: ${threadId}`);
56
+ }
57
+ return {
58
+ id: row._id,
59
+ resourceId: row.resourceId,
60
+ title: row.title ?? undefined,
61
+ metadata: row.metadata ?? undefined,
62
+ createdAt: new Date(row.createdAt),
63
+ updatedAt: new Date(row.updatedAt),
64
+ };
65
+ }
66
+ async getThreads(resourceId) {
67
+ const result = await this.threads
68
+ .find({ resourceId })
69
+ .sort({ updatedAt: -1 })
70
+ .toArray();
71
+ return result.map((row) => ({
72
+ id: row._id,
73
+ resourceId: row.resourceId,
74
+ title: row.title ?? undefined,
75
+ metadata: row.metadata ?? undefined,
76
+ createdAt: new Date(row.createdAt),
77
+ updatedAt: new Date(row.updatedAt),
78
+ }));
79
+ }
80
+ async deleteThread(threadId) {
81
+ await this.toolCalls.deleteMany({ threadId });
82
+ await this.agentMessages.deleteMany({ threadId });
83
+ await this.agentRuns.deleteMany({ threadId });
84
+ await this.threads.deleteOne({ _id: threadId });
85
+ }
86
+ async getMessages(threadId, options) {
87
+ let filter = { threadId };
88
+ if (options?.cursor) {
89
+ const cursorRow = await this.agentMessages.findOne({
90
+ _id: options.cursor,
91
+ });
92
+ if (cursorRow) {
93
+ filter.createdAt = { $lt: cursorRow.createdAt };
94
+ }
95
+ }
96
+ let msgResult;
97
+ if (options?.cursor || options?.lastN) {
98
+ const innerResult = await this.agentMessages
99
+ .find(filter)
100
+ .sort({ createdAt: -1 })
101
+ .limit(options?.lastN ?? 50)
102
+ .toArray();
103
+ innerResult.reverse();
104
+ msgResult = innerResult;
105
+ }
106
+ else {
107
+ msgResult = await this.agentMessages
108
+ .find(filter)
109
+ .sort({ createdAt: 1 })
110
+ .toArray();
111
+ }
112
+ const tcResult = await this.toolCalls
113
+ .find({ threadId })
114
+ .sort({ createdAt: 1 })
115
+ .toArray();
116
+ const tcByMessage = new Map();
117
+ for (const tc of tcResult) {
118
+ const msgId = tc.messageId;
119
+ if (!tcByMessage.has(msgId))
120
+ tcByMessage.set(msgId, []);
121
+ tcByMessage.get(msgId).push(tc);
122
+ }
123
+ const messages = [];
124
+ for (const row of msgResult) {
125
+ let parsedContent = row.content ?? undefined;
126
+ if (typeof row.content === 'string') {
127
+ try {
128
+ const parsed = JSON.parse(row.content);
129
+ if (Array.isArray(parsed)) {
130
+ parsedContent = parsed;
131
+ }
132
+ else if (typeof parsed === 'string') {
133
+ parsedContent = parsed;
134
+ }
135
+ }
136
+ catch {
137
+ // Not JSON, use raw string
138
+ }
139
+ }
140
+ const msg = {
141
+ id: row._id,
142
+ role: row.role,
143
+ content: parsedContent,
144
+ createdAt: new Date(row.createdAt),
145
+ };
146
+ const tcs = tcByMessage.get(msg.id);
147
+ if (tcs?.length) {
148
+ msg.toolCalls = tcs.map((tc) => ({
149
+ id: tc._id,
150
+ name: tc.toolName,
151
+ args: tc.args,
152
+ }));
153
+ const completed = tcs.filter((tc) => tc.result != null);
154
+ if (completed.length) {
155
+ messages.push(msg);
156
+ messages.push({
157
+ id: `tool-results-${msg.id}`,
158
+ role: 'tool',
159
+ toolResults: completed.map((tc) => ({
160
+ id: tc._id,
161
+ name: tc.toolName,
162
+ result: tc.result,
163
+ })),
164
+ createdAt: msg.createdAt,
165
+ });
166
+ continue;
167
+ }
168
+ }
169
+ messages.push(msg);
170
+ }
171
+ return messages;
172
+ }
173
+ async saveMessages(threadId, messages) {
174
+ if (messages.length === 0)
175
+ return;
176
+ const toolMessages = messages.filter((m) => m.role === 'tool');
177
+ const nonToolMessages = messages.filter((m) => m.role !== 'tool');
178
+ if (nonToolMessages.length > 0) {
179
+ await this.agentMessages.insertMany(nonToolMessages.map((msg) => ({
180
+ _id: msg.id,
181
+ threadId,
182
+ role: msg.role,
183
+ content: msg.content != null ? JSON.stringify(msg.content) : null,
184
+ createdAt: msg.createdAt ?? new Date(),
185
+ })));
186
+ }
187
+ const toolCallDocs = nonToolMessages.flatMap((msg) => msg.toolCalls?.map((tc) => ({ ...tc, messageId: msg.id })) ?? []);
188
+ if (toolCallDocs.length > 0) {
189
+ await this.toolCalls.insertMany(toolCallDocs.map((tc) => ({
190
+ _id: tc.id,
191
+ threadId,
192
+ messageId: tc.messageId,
193
+ runId: null,
194
+ toolName: tc.name,
195
+ args: tc.args,
196
+ result: null,
197
+ approvalStatus: null,
198
+ approvalType: null,
199
+ agentRunId: null,
200
+ displayToolName: null,
201
+ displayArgs: null,
202
+ createdAt: new Date(),
203
+ })));
204
+ }
205
+ for (const toolMsg of toolMessages) {
206
+ if (!toolMsg.toolResults)
207
+ continue;
208
+ for (const tr of toolMsg.toolResults) {
209
+ await this.toolCalls.updateOne({ _id: tr.id }, { $set: { result: tr.result } });
210
+ }
211
+ }
212
+ await this.threads.updateOne({ _id: threadId }, { $set: { updatedAt: new Date() } });
213
+ }
214
+ async getWorkingMemory(id, scope) {
215
+ const row = await this.workingMemory.findOne({ id, scope });
216
+ if (!row)
217
+ return null;
218
+ return row.data;
219
+ }
220
+ async saveWorkingMemory(id, scope, data) {
221
+ await this.workingMemory.updateOne({ id, scope }, {
222
+ $set: {
223
+ data,
224
+ updatedAt: new Date(),
225
+ },
226
+ $setOnInsert: {
227
+ id,
228
+ scope,
229
+ },
230
+ }, { upsert: true });
231
+ }
232
+ async createRun(run) {
233
+ const runId = crypto.randomUUID();
234
+ await this.agentRuns.insertOne({
235
+ _id: runId,
236
+ agentName: run.agentName,
237
+ threadId: run.threadId,
238
+ resourceId: run.resourceId,
239
+ status: run.status,
240
+ errorMessage: run.errorMessage ?? null,
241
+ suspendReason: run.suspendReason ?? null,
242
+ missingRpcs: run.missingRpcs ?? null,
243
+ usageInputTokens: run.usage.inputTokens,
244
+ usageOutputTokens: run.usage.outputTokens,
245
+ usageModel: run.usage.model,
246
+ createdAt: run.createdAt,
247
+ updatedAt: run.updatedAt,
248
+ });
249
+ if (run.pendingApprovals?.length) {
250
+ await this.setApprovals(runId, run.pendingApprovals);
251
+ }
252
+ return runId;
253
+ }
254
+ async updateRun(runId, updates) {
255
+ const setValues = { updatedAt: new Date() };
256
+ if (updates.status !== undefined) {
257
+ setValues.status = updates.status;
258
+ }
259
+ if (updates.errorMessage !== undefined) {
260
+ setValues.errorMessage = updates.errorMessage;
261
+ }
262
+ if (updates.suspendReason !== undefined) {
263
+ setValues.suspendReason = updates.suspendReason;
264
+ }
265
+ if (updates.missingRpcs !== undefined) {
266
+ setValues.missingRpcs = updates.missingRpcs;
267
+ }
268
+ if (updates.usage !== undefined) {
269
+ setValues.usageInputTokens = updates.usage.inputTokens;
270
+ setValues.usageOutputTokens = updates.usage.outputTokens;
271
+ setValues.usageModel = updates.usage.model;
272
+ }
273
+ await this.agentRuns.updateOne({ _id: runId }, { $set: setValues });
274
+ if (updates.pendingApprovals !== undefined) {
275
+ await this.toolCalls.updateMany({ runId, approvalStatus: { $ne: null } }, {
276
+ $set: {
277
+ approvalStatus: null,
278
+ runId: null,
279
+ approvalType: null,
280
+ agentRunId: null,
281
+ displayToolName: null,
282
+ displayArgs: null,
283
+ },
284
+ });
285
+ if (updates.pendingApprovals.length) {
286
+ await this.setApprovals(runId, updates.pendingApprovals);
287
+ }
288
+ }
289
+ }
290
+ async getRun(runId) {
291
+ const row = await this.agentRuns.findOne({ _id: runId });
292
+ if (!row)
293
+ return null;
294
+ const approvals = await this.toolCalls
295
+ .find({ runId, approvalStatus: 'pending' })
296
+ .toArray();
297
+ return this.mapRunRow(row, approvals);
298
+ }
299
+ async getRunsByThread(threadId) {
300
+ const result = await this.agentRuns
301
+ .find({ threadId })
302
+ .sort({ createdAt: -1 })
303
+ .toArray();
304
+ const runs = [];
305
+ for (const row of result) {
306
+ const approvals = await this.toolCalls
307
+ .find({ runId: row._id, approvalStatus: 'pending' })
308
+ .toArray();
309
+ runs.push(this.mapRunRow(row, approvals));
310
+ }
311
+ return runs;
312
+ }
313
+ async setApprovals(runId, approvals) {
314
+ for (const a of approvals) {
315
+ if (a.type === 'agent-call') {
316
+ await this.toolCalls.updateOne({ _id: a.toolCallId }, {
317
+ $set: {
318
+ approvalStatus: 'pending',
319
+ runId,
320
+ approvalType: 'agent-call',
321
+ agentRunId: a.agentRunId,
322
+ displayToolName: a.displayToolName,
323
+ displayArgs: a.displayArgs,
324
+ },
325
+ });
326
+ }
327
+ else {
328
+ await this.toolCalls.updateOne({ _id: a.toolCallId }, {
329
+ $set: {
330
+ approvalStatus: 'pending',
331
+ runId,
332
+ approvalType: 'tool-call',
333
+ },
334
+ });
335
+ }
336
+ }
337
+ }
338
+ mapRunRow(row, approvalRows) {
339
+ const pendingApprovals = approvalRows?.length
340
+ ? approvalRows.map((a) => {
341
+ if (a.approvalType === 'agent-call') {
342
+ return {
343
+ type: 'agent-call',
344
+ toolCallId: a._id,
345
+ agentName: a.toolName,
346
+ agentRunId: a.agentRunId,
347
+ displayToolName: a.displayToolName,
348
+ displayArgs: a.displayArgs,
349
+ };
350
+ }
351
+ return {
352
+ type: 'tool-call',
353
+ toolCallId: a._id,
354
+ toolName: a.toolName,
355
+ args: a.args,
356
+ };
357
+ })
358
+ : undefined;
359
+ return {
360
+ runId: row._id,
361
+ agentName: row.agentName,
362
+ threadId: row.threadId,
363
+ resourceId: row.resourceId,
364
+ status: row.status,
365
+ errorMessage: row.errorMessage ?? undefined,
366
+ suspendReason: row.suspendReason,
367
+ missingRpcs: row.missingRpcs ?? undefined,
368
+ pendingApprovals,
369
+ usage: {
370
+ inputTokens: Number(row.usageInputTokens),
371
+ outputTokens: Number(row.usageOutputTokens),
372
+ model: row.usageModel,
373
+ },
374
+ createdAt: new Date(row.createdAt),
375
+ updatedAt: new Date(row.updatedAt),
376
+ };
377
+ }
378
+ async findRunByToolCallId(toolCallId) {
379
+ const tc = await this.toolCalls.findOne({
380
+ _id: toolCallId,
381
+ approvalStatus: 'pending',
382
+ });
383
+ if (!tc || !tc.runId)
384
+ return null;
385
+ const run = await this.getRun(tc.runId);
386
+ if (!run)
387
+ return null;
388
+ let approval;
389
+ if (tc.approvalType === 'agent-call') {
390
+ approval = {
391
+ type: 'agent-call',
392
+ toolCallId: tc._id,
393
+ agentName: tc.toolName,
394
+ agentRunId: tc.agentRunId,
395
+ displayToolName: tc.displayToolName,
396
+ displayArgs: tc.displayArgs,
397
+ };
398
+ }
399
+ else {
400
+ approval = {
401
+ type: 'tool-call',
402
+ toolCallId: tc._id,
403
+ toolName: tc.toolName,
404
+ args: tc.args,
405
+ };
406
+ }
407
+ return { run, approval };
408
+ }
409
+ async resolveApproval(toolCallId, status) {
410
+ // Only the caller that moves the tool call off 'pending' has claimed it.
411
+ const result = await this.toolCalls.updateOne({ _id: toolCallId, approvalStatus: 'pending' }, { $set: { approvalStatus: status } });
412
+ return result.modifiedCount > 0;
413
+ }
414
+ async saveScore(score) {
415
+ await this.agentRunScores.insertOne({
416
+ _id: crypto.randomUUID(),
417
+ runId: score.runId,
418
+ scorerName: score.scorerName,
419
+ score: score.score,
420
+ reason: score.reason ?? null,
421
+ metadata: score.metadata ?? null,
422
+ createdAt: new Date(),
423
+ });
424
+ }
425
+ async getScores(runId) {
426
+ const rows = await this.agentRunScores
427
+ .find({ runId })
428
+ .sort({ createdAt: 1 })
429
+ .toArray();
430
+ return rows.map((row) => ({
431
+ runId: row.runId,
432
+ scorerName: row.scorerName,
433
+ score: row.score,
434
+ reason: row.reason ?? undefined,
435
+ metadata: row.metadata ?? undefined,
436
+ createdAt: row.createdAt,
437
+ }));
438
+ }
439
+ async close() { }
440
+ }
@@ -1,5 +1,6 @@
1
- import type { AIStorageService, AIRunStateService, CreateRunInput } from '@pikku/core/services';
1
+ import type { AIStorageService, AIRunStateService, CreateRunInput, SaveScoreInput } from '@pikku/core/services';
2
2
  import type { AIThread, AIMessage, AgentRunState } from '@pikku/core/ai-agent';
3
+ import type { AIRunScore } from '@pikku/core/ai-scorer';
3
4
  import type { Db } from 'mongodb';
4
5
  export declare class MongoDBAIStorageService implements AIStorageService, AIRunStateService {
5
6
  private db;
@@ -9,6 +10,7 @@ export declare class MongoDBAIStorageService implements AIStorageService, AIRunS
9
10
  private toolCalls;
10
11
  private workingMemory;
11
12
  private aiRuns;
13
+ private aiRunScores;
12
14
  constructor(db: Db);
13
15
  init(): Promise<void>;
14
16
  createThread(resourceId: string, options?: {
@@ -37,5 +39,7 @@ export declare class MongoDBAIStorageService implements AIStorageService, AIRunS
37
39
  approval: NonNullable<AgentRunState['pendingApprovals']>[number];
38
40
  } | null>;
39
41
  resolveApproval(toolCallId: string, status: 'approved' | 'denied'): Promise<boolean>;
42
+ saveScore(score: SaveScoreInput): Promise<void>;
43
+ getScores(runId: string): Promise<AIRunScore[]>;
40
44
  close(): Promise<void>;
41
45
  }