@pikku/kysely 0.13.15 → 0.13.16

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 (54) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/dist/src/index.d.ts +7 -7
  3. package/dist/src/index.js +4 -4
  4. package/dist/src/kysely-agent-run-scores.d.ts +10 -0
  5. package/dist/src/kysely-agent-run-scores.js +34 -0
  6. package/dist/src/kysely-agent-run-service.d.ts +22 -0
  7. package/dist/src/kysely-agent-run-service.js +190 -0
  8. package/dist/src/kysely-agent-run-state-service.d.ts +24 -0
  9. package/dist/src/kysely-agent-run-state-service.js +169 -0
  10. package/dist/src/kysely-agent-storage-service.d.ts +40 -0
  11. package/dist/src/kysely-agent-storage-service.js +533 -0
  12. package/dist/src/kysely-ai-agent-run-service.d.ts +2 -1
  13. package/dist/src/kysely-ai-run-state-service.d.ts +2 -1
  14. package/dist/src/kysely-audit-service.d.ts +3 -1
  15. package/dist/src/kysely-deployment-service.d.ts +1 -1
  16. package/dist/src/kysely-deployment-service.js +2 -2
  17. package/dist/src/kysely-eventhub-store.d.ts +1 -1
  18. package/dist/src/kysely-eventhub-store.js +1 -1
  19. package/dist/src/kysely-scope-service.d.ts +4 -3
  20. package/dist/src/kysely-tables.d.ts +13 -13
  21. package/dist/src/kysely-virtual-user-run-store.d.ts +1 -1
  22. package/dist/src/kysely-webhook-service.d.ts +2 -1
  23. package/dist/src/kysely-webhook-service.js +2 -1
  24. package/dist/src/kysely-workflow-mirror.d.ts +2 -2
  25. package/dist/src/kysely-workflow-run-service.d.ts +1 -1
  26. package/dist/src/kysely-workflow-service.d.ts +3 -2
  27. package/dist/src/kysely-workflow-service.js +1 -1
  28. package/dist/src/schema/agent.schema.d.ts +12 -0
  29. package/dist/src/schema/agent.schema.js +102 -0
  30. package/dist/src/schema/index.js +4 -4
  31. package/dist/src/serialize-plugin.d.ts +0 -4
  32. package/dist/src/serialize-plugin.js +0 -4
  33. package/dist/tsconfig.tsbuildinfo +1 -1
  34. package/package.json +2 -2
  35. package/src/index.ts +9 -7
  36. package/src/{kysely-ai-run-scores.ts → kysely-agent-run-scores.ts} +5 -5
  37. package/src/{kysely-ai-agent-run-service.ts → kysely-agent-run-service.ts} +17 -18
  38. package/src/{kysely-ai-run-state-service.ts → kysely-agent-run-state-service.ts} +16 -15
  39. package/src/{kysely-ai-storage-service.ts → kysely-agent-storage-service.ts} +53 -43
  40. package/src/kysely-audit-service.ts +4 -2
  41. package/src/kysely-deployment-service.ts +3 -3
  42. package/src/kysely-eventhub-store.ts +1 -1
  43. package/src/kysely-scope-service.ts +7 -4
  44. package/src/kysely-services.test.ts +11 -11
  45. package/src/kysely-tables.ts +13 -13
  46. package/src/kysely-virtual-user-run-store.ts +2 -2
  47. package/src/kysely-webhook-service.ts +2 -2
  48. package/src/kysely-workflow-mirror.ts +2 -2
  49. package/src/kysely-workflow-run-service.ts +1 -1
  50. package/src/kysely-workflow-service.ts +3 -12
  51. package/src/schema/{ai.schema.ts → agent.schema.ts} +29 -29
  52. package/src/schema/index.ts +4 -4
  53. package/src/schema/schema.test.ts +6 -6
  54. package/src/serialize-plugin.ts +0 -5
package/CHANGELOG.md CHANGED
@@ -1,3 +1,97 @@
1
+ ## 0.13.16
2
+
3
+ ### Patch Changes
4
+
5
+ - 7406bfe: Rename the agent runtime from `AI*` to `Agent*` (#596)
6
+
7
+ `AI` described the model provider, not the thing being named. Every symbol that
8
+ belongs to the agent runtime now says `Agent`; the symbols that genuinely wrap a
9
+ model provider — `AIEmbeddingService`, `AIProviderOptions`, `AIEmbedParams`,
10
+ `AITranscriptionParams`, `AIGenerateImageParams` and their siblings, and the
11
+ `@pikku/ai-vercel` / `@pikku/ai-deepinfra` / `@pikku/ai-voice` packages — keep
12
+ their names.
13
+
14
+ **Wiring**
15
+ - `pikkuAIAgent` → `pikkuAgent`, `pikkuAIScorer` → `pikkuAgentScorer`,
16
+ `pikkuAIJudge` → `pikkuAgentJudge`
17
+ - `CoreAIAgent` → `CoreAgent`, `AIAgentInput` → `AgentInput`, `AIAgentStep` →
18
+ `AgentStep`, `AIMessage` → `AgentMessage`, and the rest of the agent types
19
+ - `AIAgentRunnerService` → `AgentRunnerService`, `AIStorageService` →
20
+ `AgentStorageService`, `AIRunStateService` → `AgentRunStateService`
21
+
22
+ **Entry points**
23
+
24
+ `@pikku/core/agent` → `@pikku/core/agent`, `@pikku/core/agent-scorer` →
25
+ `@pikku/core/agent-scorer`.
26
+
27
+ **Queues**
28
+
29
+ The scorer queues are now `agent-score-fast` and `agent-score-slow`. Drain the
30
+ old `ai-score-fast` / `ai-score-slow` queues before deploying — jobs still
31
+ sitting on them when the new workers start will never be picked up.
32
+
33
+ **Scaffolds**
34
+
35
+ The agent scaffold pikku wrote for your project — `<scaffold>/agent/agent.gen.ts`
36
+ and its schemas file — imports `@pikku/core/ai-agent`, which no longer exists. A
37
+ scaffold is normally written once and then left alone, so `pikku all` would find
38
+ it present and leave the broken import in place. It now deletes an agent scaffold
39
+ importing either removed entry point and regenerates it in the same run. Anything
40
+ you added to that file goes with it, so move local edits out first.
41
+
42
+ **Database**
43
+
44
+ The agent tables are renamed: `ai_threads`, `ai_message`, `ai_tool_call`,
45
+ `ai_working_memory`, `ai_run` and `ai_run_score` become `agent_threads`,
46
+ `agent_message`, `agent_tool_call`, `agent_working_memory`, `agent_run` and
47
+ `agent_run_score`, along with their indexes and the `ai_working_memory_pk`
48
+ constraint. The same rename applies to the MongoDB collections.
49
+
50
+ `ensurePikkuSchema` creates tables it cannot find, so an existing database will
51
+ get empty `agent_*` tables and leave the old data stranded in `ai_*`. Rename
52
+ them before the first boot on the new version:
53
+
54
+ ```sql
55
+ ALTER TABLE ai_threads RENAME TO agent_threads;
56
+ ALTER TABLE ai_message RENAME TO agent_message;
57
+ ALTER TABLE ai_tool_call RENAME TO agent_tool_call;
58
+ ALTER TABLE ai_working_memory RENAME TO agent_working_memory;
59
+ ALTER TABLE ai_run RENAME TO agent_run;
60
+ ALTER TABLE ai_run_score RENAME TO agent_run_score;
61
+ ```
62
+
63
+ - 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.
64
+
65
+ 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.
66
+
67
+ 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.
68
+
69
+ - a7fcd2e: Declare dependencies that were imported but missing from `package.json`
70
+
71
+ `@pikku/openapi-parser` and `@pikku/better-auth` imported `zod`, `@pikku/next`
72
+ imported `path-to-regexp`, `@pikku/cli` imported `kysely`, and
73
+ `@pikku/assistant-ui` imported `rxjs`, none of which were declared. Each
74
+ resolved through Yarn hoisting inside the monorepo and would fail for anyone
75
+ installing the package on its own.
76
+
77
+ `rxjs`, `kysely` and `path-to-regexp` reach consumers through public
78
+ signatures — `Observable<BaseEvent>` is the return type of a published method,
79
+ and `createCoercionPlugin` returns a `KyselyPlugin` — so they are runtime
80
+ dependencies rather than build-only ones.
81
+
82
+ `@pikku/assistant-ui` pins `rxjs` to the exact `7.8.1` that `@ag-ui/client`
83
+ pins, rather than a caret range. The two packages exchange `Observable`s, so a
84
+ range that floats to a second copy gives them two incompatible `Observable`
85
+ types.
86
+
87
+ `@pikku/kysely` also drops `SqliteSerializePlugin`, an alias of
88
+ `SerializePlugin` that has been marked `@deprecated` in favour of it. Use
89
+ `SerializePlugin`.
90
+
91
+ - Updated dependencies [7406bfe]
92
+ - Updated dependencies [6794681]
93
+ - @pikku/core@0.12.84
94
+
1
95
  ## 0.13.15
2
96
 
3
97
  ### Patch Changes
@@ -4,9 +4,9 @@ export { KyselyWorkflowService } from './kysely-workflow-service.js';
4
4
  export { KyselyWorkflowRunService } from './kysely-workflow-run-service.js';
5
5
  export { KyselyWorkflowMirror } from './kysely-workflow-mirror.js';
6
6
  export { KyselyDeploymentService } from './kysely-deployment-service.js';
7
- export { KyselyAIStorageService } from './kysely-ai-storage-service.js';
8
- export { KyselyAgentRunService } from './kysely-ai-agent-run-service.js';
9
- export { KyselyAIRunStateService } from './kysely-ai-run-state-service.js';
7
+ export { KyselyAgentStorageService } from './kysely-agent-storage-service.js';
8
+ export { KyselyAgentRunService } from './kysely-agent-run-service.js';
9
+ export { KyselyAgentRunStateService } from './kysely-agent-run-state-service.js';
10
10
  export { KyselySecretService } from './kysely-secret-service.js';
11
11
  export { KyselyCredentialService } from './kysely-credential-service.js';
12
12
  export { KyselySessionStore } from './kysely-session-store.js';
@@ -15,9 +15,9 @@ export { KyselyWebhookService } from './kysely-webhook-service.js';
15
15
  export { createAuditedKysely, type CreateAuditedKyselyOptions, } from './create-audited-kysely.js';
16
16
  export { KyselyAuditService } from './kysely-audit-service.js';
17
17
  export { KyselyVirtualUserRunStore } from './kysely-virtual-user-run-store.js';
18
- export { SerializePlugin, SqliteSerializePlugin, BaseSerializePlugin, type Serializer, type Deserializer, } from './serialize-plugin.js';
18
+ export { SerializePlugin, BaseSerializePlugin, type Serializer, type Deserializer, } from './serialize-plugin.js';
19
19
  export { auditSchema, pikkuSchemas, applyPikkuSchemas, compilePikkuSchemas, ensurePikkuSchema, type EnsureOutcome, resolveRequirements, type PikkuSchema, type RequiredTypes, type UnmetRequirement, type SchemaStatementFactory, } from './schema/index.js';
20
20
  export type { KyselyPikkuDB } from './kysely-tables.js';
21
- export type { WorkflowRunService } from '@pikku/core/workflow';
22
- export type { AgentRunService, AgentRunRow } from '@pikku/core/ai-agent';
23
- export type { VirtualUserRunStore, VirtualUserRunRecord, } from '@pikku/core/virtual-user';
21
+ export type { WorkflowRunService } from '@pikku/core/ecosystem/workflow';
22
+ export type { AgentRunService, AgentRunRow, } from '@pikku/core/ecosystem/agent';
23
+ export type { VirtualUserRunStore, VirtualUserRunRecord, } from '@pikku/core/ecosystem/virtual-user';
package/dist/src/index.js CHANGED
@@ -4,9 +4,9 @@ export { KyselyWorkflowService } from './kysely-workflow-service.js';
4
4
  export { KyselyWorkflowRunService } from './kysely-workflow-run-service.js';
5
5
  export { KyselyWorkflowMirror } from './kysely-workflow-mirror.js';
6
6
  export { KyselyDeploymentService } from './kysely-deployment-service.js';
7
- export { KyselyAIStorageService } from './kysely-ai-storage-service.js';
8
- export { KyselyAgentRunService } from './kysely-ai-agent-run-service.js';
9
- export { KyselyAIRunStateService } from './kysely-ai-run-state-service.js';
7
+ export { KyselyAgentStorageService } from './kysely-agent-storage-service.js';
8
+ export { KyselyAgentRunService } from './kysely-agent-run-service.js';
9
+ export { KyselyAgentRunStateService } from './kysely-agent-run-state-service.js';
10
10
  export { KyselySecretService } from './kysely-secret-service.js';
11
11
  export { KyselyCredentialService } from './kysely-credential-service.js';
12
12
  export { KyselySessionStore } from './kysely-session-store.js';
@@ -15,5 +15,5 @@ export { KyselyWebhookService } from './kysely-webhook-service.js';
15
15
  export { createAuditedKysely, } from './create-audited-kysely.js';
16
16
  export { KyselyAuditService } from './kysely-audit-service.js';
17
17
  export { KyselyVirtualUserRunStore } from './kysely-virtual-user-run-store.js';
18
- export { SerializePlugin, SqliteSerializePlugin, BaseSerializePlugin, } from './serialize-plugin.js';
18
+ export { SerializePlugin, BaseSerializePlugin, } from './serialize-plugin.js';
19
19
  export { auditSchema, pikkuSchemas, applyPikkuSchemas, compilePikkuSchemas, ensurePikkuSchema, resolveRequirements, } from './schema/index.js';
@@ -0,0 +1,10 @@
1
+ import type { Kysely } from 'kysely';
2
+ import type { KyselyPikkuDB } from './kysely-tables.js';
3
+ import type { SaveScoreInput } from '@pikku/core/services';
4
+ import type { AgentRunScore } from '@pikku/core/agent-scorer';
5
+ /**
6
+ * Shared by the two Kysely services that implement `AgentRunStateService` — they
7
+ * differ in how they store approvals, not in how they store grades.
8
+ */
9
+ export declare const saveRunScore: (db: Kysely<KyselyPikkuDB>, score: SaveScoreInput) => Promise<void>;
10
+ export declare const getRunScores: (db: Kysely<KyselyPikkuDB>, runId: string) => Promise<AgentRunScore[]>;
@@ -0,0 +1,34 @@
1
+ import { parseJson } from './kysely-json.js';
2
+ /**
3
+ * Shared by the two Kysely services that implement `AgentRunStateService` — they
4
+ * differ in how they store approvals, not in how they store grades.
5
+ */
6
+ export const saveRunScore = async (db, score) => {
7
+ await db
8
+ .insertInto('agentRunScore')
9
+ .values({
10
+ id: crypto.randomUUID(),
11
+ runId: score.runId,
12
+ scorerName: score.scorerName,
13
+ score: score.score,
14
+ reason: score.reason ?? null,
15
+ metadata: score.metadata ? JSON.stringify(score.metadata) : null,
16
+ })
17
+ .execute();
18
+ };
19
+ export const getRunScores = async (db, runId) => {
20
+ const rows = await db
21
+ .selectFrom('agentRunScore')
22
+ .selectAll()
23
+ .where('runId', '=', runId)
24
+ .orderBy('createdAt', 'asc')
25
+ .execute();
26
+ return rows.map((row) => ({
27
+ runId: row.runId,
28
+ scorerName: row.scorerName,
29
+ score: Number(row.score),
30
+ reason: row.reason ?? undefined,
31
+ metadata: parseJson(row.metadata),
32
+ createdAt: new Date(row.createdAt),
33
+ }));
34
+ };
@@ -0,0 +1,22 @@
1
+ import type { AgentThread, AgentMessage } from '@pikku/core/agent';
2
+ import type { AgentRunRow, AgentRunService } from '@pikku/core/ecosystem/agent';
3
+ import type { Kysely } from 'kysely';
4
+ import type { KyselyPikkuDB } from './kysely-tables.js';
5
+ export declare class KyselyAgentRunService implements AgentRunService {
6
+ private db;
7
+ constructor(db: Kysely<KyselyPikkuDB>);
8
+ listThreads(options?: {
9
+ agentName?: string;
10
+ resourceId?: string;
11
+ owners?: string[];
12
+ limit?: number;
13
+ offset?: number;
14
+ }): Promise<AgentThread[]>;
15
+ getThread(threadId: string): Promise<AgentThread | null>;
16
+ getThreadMessages(threadId: string): Promise<AgentMessage[]>;
17
+ getThreadRuns(threadId: string): Promise<AgentRunRow[]>;
18
+ deleteThread(threadId: string): Promise<boolean>;
19
+ getDistinctAgentNames(): Promise<string[]>;
20
+ private mapThreadRow;
21
+ private mapRunRow;
22
+ }
@@ -0,0 +1,190 @@
1
+ import { sql } from 'kysely';
2
+ import { parseJson } from './kysely-json.js';
3
+ // Owner ids routinely contain `_` (e.g. `user_123`), which LIKE treats as a
4
+ // single-character wildcard — so a raw prefix would match a foreign owner.
5
+ const escapeLikePattern = (value) => value.replace(/[\\%_]/g, (char) => `\\${char}`);
6
+ export class KyselyAgentRunService {
7
+ db;
8
+ constructor(db) {
9
+ this.db = db;
10
+ }
11
+ async listThreads(options) {
12
+ const { agentName, resourceId, owners, limit = 50, offset = 0, } = options ?? {};
13
+ // An owners constraint is an authorization boundary, so an empty list must
14
+ // match nothing rather than degrade to "no filter".
15
+ if (owners && owners.length === 0)
16
+ return [];
17
+ let query = this.db
18
+ .selectFrom('agentThreads as t')
19
+ .select([
20
+ 't.id',
21
+ 't.resourceId',
22
+ 't.title',
23
+ 't.metadata',
24
+ 't.createdAt',
25
+ 't.updatedAt',
26
+ ]);
27
+ if (agentName) {
28
+ query = query.where('t.id', 'in', this.db
29
+ .selectFrom('agentRun')
30
+ .select('threadId')
31
+ .where('agentName', '=', agentName)
32
+ .distinct());
33
+ }
34
+ if (resourceId) {
35
+ query = query.where('t.resourceId', '=', resourceId);
36
+ }
37
+ if (owners) {
38
+ query = query.where((eb) => eb.or(owners.flatMap((owner) => [
39
+ eb('t.resourceId', '=', owner),
40
+ eb('t.resourceId', 'like', sql `${`${escapeLikePattern(owner)}:%`} escape '\\'`),
41
+ ])));
42
+ }
43
+ const result = await query
44
+ .orderBy('t.updatedAt', 'desc')
45
+ .limit(limit)
46
+ .offset(offset)
47
+ .execute();
48
+ return result.map((row) => this.mapThreadRow(row));
49
+ }
50
+ async getThread(threadId) {
51
+ const row = await this.db
52
+ .selectFrom('agentThreads')
53
+ .select([
54
+ 'id',
55
+ 'resourceId',
56
+ 'title',
57
+ 'metadata',
58
+ 'createdAt',
59
+ 'updatedAt',
60
+ ])
61
+ .where('id', '=', threadId)
62
+ .executeTakeFirst();
63
+ if (!row)
64
+ return null;
65
+ return this.mapThreadRow(row);
66
+ }
67
+ async getThreadMessages(threadId) {
68
+ const [msgResult, tcResult] = await Promise.all([
69
+ this.db
70
+ .selectFrom('agentMessage')
71
+ .select(['id', 'role', 'content', 'createdAt'])
72
+ .where('threadId', '=', threadId)
73
+ .orderBy('createdAt', 'asc')
74
+ .execute(),
75
+ this.db
76
+ .selectFrom('agentToolCall')
77
+ .select(['id', 'messageId', 'toolName', 'args', 'result'])
78
+ .where('threadId', '=', threadId)
79
+ .orderBy('createdAt', 'asc')
80
+ .execute(),
81
+ ]);
82
+ const tcByMessage = new Map();
83
+ for (const tc of tcResult) {
84
+ const msgId = tc.messageId;
85
+ if (!tcByMessage.has(msgId))
86
+ tcByMessage.set(msgId, []);
87
+ tcByMessage.get(msgId).push(tc);
88
+ }
89
+ const messages = [];
90
+ for (const row of msgResult) {
91
+ const msg = {
92
+ id: row.id,
93
+ role: row.role,
94
+ content: row.content ?? undefined,
95
+ createdAt: new Date(row.createdAt),
96
+ };
97
+ const tcs = tcByMessage.get(msg.id);
98
+ if (tcs?.length) {
99
+ msg.toolCalls = tcs.map((tc) => ({
100
+ id: tc.id,
101
+ name: tc.toolName,
102
+ args: parseJson(tc.args),
103
+ }));
104
+ const completed = tcs.filter((tc) => tc.result != null);
105
+ if (completed.length) {
106
+ messages.push(msg);
107
+ messages.push({
108
+ id: `tool-results-${msg.id}`,
109
+ role: 'tool',
110
+ toolResults: completed.map((tc) => ({
111
+ id: tc.id,
112
+ name: tc.toolName,
113
+ result: tc.result,
114
+ })),
115
+ createdAt: msg.createdAt,
116
+ });
117
+ continue;
118
+ }
119
+ }
120
+ messages.push(msg);
121
+ }
122
+ return messages;
123
+ }
124
+ async getThreadRuns(threadId) {
125
+ const result = await this.db
126
+ .selectFrom('agentRun')
127
+ .select([
128
+ 'runId',
129
+ 'agentName',
130
+ 'threadId',
131
+ 'resourceId',
132
+ 'status',
133
+ 'errorMessage',
134
+ 'suspendReason',
135
+ 'missingRpcs',
136
+ 'usageInputTokens',
137
+ 'usageOutputTokens',
138
+ 'usageModel',
139
+ 'createdAt',
140
+ 'updatedAt',
141
+ ])
142
+ .where('threadId', '=', threadId)
143
+ .orderBy('createdAt', 'desc')
144
+ .execute();
145
+ return result.map((row) => this.mapRunRow(row));
146
+ }
147
+ async deleteThread(threadId) {
148
+ const result = await this.db
149
+ .deleteFrom('agentThreads')
150
+ .where('id', '=', threadId)
151
+ .executeTakeFirst();
152
+ return BigInt(result.numDeletedRows) > 0n;
153
+ }
154
+ async getDistinctAgentNames() {
155
+ const result = await this.db
156
+ .selectFrom('agentRun')
157
+ .select('agentName')
158
+ .distinct()
159
+ .orderBy('agentName')
160
+ .execute();
161
+ return result.map((row) => row.agentName);
162
+ }
163
+ mapThreadRow(row) {
164
+ return {
165
+ id: row.id,
166
+ resourceId: row.resourceId,
167
+ title: row.title ?? undefined,
168
+ metadata: parseJson(row.metadata),
169
+ createdAt: new Date(row.createdAt),
170
+ updatedAt: new Date(row.updatedAt),
171
+ };
172
+ }
173
+ mapRunRow(row) {
174
+ return {
175
+ runId: row.runId,
176
+ agentName: row.agentName,
177
+ threadId: row.threadId,
178
+ resourceId: row.resourceId,
179
+ status: row.status,
180
+ errorMessage: row.errorMessage ?? undefined,
181
+ suspendReason: row.suspendReason ?? undefined,
182
+ missingRpcs: parseJson(row.missingRpcs),
183
+ usageInputTokens: Number(row.usageInputTokens),
184
+ usageOutputTokens: Number(row.usageOutputTokens),
185
+ usageModel: row.usageModel,
186
+ createdAt: new Date(row.createdAt),
187
+ updatedAt: new Date(row.updatedAt),
188
+ };
189
+ }
190
+ }
@@ -0,0 +1,24 @@
1
+ import type { Kysely } from 'kysely';
2
+ import type { KyselyPikkuDB } from './kysely-tables.js';
3
+ import type { AgentRunStateService, CreateRunInput, SaveScoreInput } from '@pikku/core/services';
4
+ import type { AgentRunState } from '@pikku/core/agent';
5
+ import type { PendingApproval } from '@pikku/core/ecosystem/agent';
6
+ import type { AgentRunScore } from '@pikku/core/agent-scorer';
7
+ export declare class KyselyAgentRunStateService implements AgentRunStateService {
8
+ private db;
9
+ private initialized;
10
+ constructor(db: Kysely<KyselyPikkuDB>);
11
+ init(): Promise<void>;
12
+ createRun(run: CreateRunInput): Promise<string>;
13
+ updateRun(runId: string, updates: Partial<AgentRunState>): Promise<void>;
14
+ getRun(runId: string): Promise<AgentRunState | null>;
15
+ getRunsByThread(threadId: string): Promise<AgentRunState[]>;
16
+ resolveApproval(toolCallId: string, status: 'approved' | 'denied'): Promise<boolean>;
17
+ findRunByToolCallId(toolCallId: string): Promise<{
18
+ run: AgentRunState;
19
+ approval: PendingApproval;
20
+ } | null>;
21
+ saveScore(score: SaveScoreInput): Promise<void>;
22
+ getScores(runId: string): Promise<AgentRunScore[]>;
23
+ private toRunState;
24
+ }
@@ -0,0 +1,169 @@
1
+ import { sql } from 'kysely';
2
+ import { ensurePikkuSchema } from './schema/index.js';
3
+ import { agentSchema } from './schema/agent.schema.js';
4
+ import { getRunScores, saveRunScore } from './kysely-agent-run-scores.js';
5
+ export class KyselyAgentRunStateService {
6
+ db;
7
+ initialized = false;
8
+ constructor(db) {
9
+ this.db = db;
10
+ }
11
+ async init() {
12
+ if (this.initialized)
13
+ return;
14
+ await ensurePikkuSchema(this.db, agentSchema);
15
+ this.initialized = true;
16
+ }
17
+ async createRun(run) {
18
+ const runId = `run-${crypto.randomUUID()}`;
19
+ await this.db
20
+ .insertInto('agentRun')
21
+ .values({
22
+ runId,
23
+ agentName: run.agentName,
24
+ threadId: run.threadId,
25
+ resourceId: run.resourceId,
26
+ status: run.status ?? 'running',
27
+ errorMessage: run.errorMessage ?? null,
28
+ suspendReason: run.suspendReason ?? null,
29
+ missingRpcs: run.missingRpcs ? JSON.stringify(run.missingRpcs) : null,
30
+ usageInputTokens: run.usage?.inputTokens ?? 0,
31
+ usageOutputTokens: run.usage?.outputTokens ?? 0,
32
+ usageModel: run.usage?.model ?? '',
33
+ })
34
+ .execute();
35
+ return runId;
36
+ }
37
+ async updateRun(runId, updates) {
38
+ const values = {
39
+ updatedAt: sql `CURRENT_TIMESTAMP`,
40
+ };
41
+ if (updates.status !== undefined)
42
+ values.status = updates.status;
43
+ if (updates.errorMessage !== undefined)
44
+ values.errorMessage = updates.errorMessage;
45
+ if (updates.suspendReason !== undefined)
46
+ values.suspendReason = updates.suspendReason;
47
+ if (updates.missingRpcs !== undefined)
48
+ values.missingRpcs = JSON.stringify(updates.missingRpcs);
49
+ if (updates.pendingApprovals !== undefined)
50
+ values.pendingApprovals = JSON.stringify(updates.pendingApprovals);
51
+ if (updates.usage) {
52
+ values.usageInputTokens = updates.usage.inputTokens;
53
+ values.usageOutputTokens = updates.usage.outputTokens;
54
+ values.usageModel = updates.usage.model;
55
+ }
56
+ await this.db
57
+ .updateTable('agentRun')
58
+ .set(values)
59
+ .where('runId', '=', runId)
60
+ .execute();
61
+ }
62
+ async getRun(runId) {
63
+ const row = await this.db
64
+ .selectFrom('agentRun')
65
+ .selectAll()
66
+ .where('runId', '=', runId)
67
+ .executeTakeFirst();
68
+ return row ? this.toRunState(row) : null;
69
+ }
70
+ async getRunsByThread(threadId) {
71
+ const rows = await this.db
72
+ .selectFrom('agentRun')
73
+ .selectAll()
74
+ .where('threadId', '=', threadId)
75
+ .orderBy('createdAt', 'desc')
76
+ .execute();
77
+ return rows.map((r) => this.toRunState(r));
78
+ }
79
+ async resolveApproval(toolCallId, status) {
80
+ const rows = await this.db
81
+ .selectFrom('agentRun')
82
+ .select(['runId', 'pendingApprovals'])
83
+ .where('status', '=', 'suspended')
84
+ .execute();
85
+ for (const row of rows) {
86
+ let approvals = [];
87
+ if (row.pendingApprovals) {
88
+ try {
89
+ approvals = JSON.parse(row.pendingApprovals);
90
+ }
91
+ catch {
92
+ console.warn(`Failed to parse pendingApprovals for run ${row.runId}, treating as empty`);
93
+ }
94
+ }
95
+ const filtered = approvals.filter((a) => a.toolCallId !== toolCallId);
96
+ if (filtered.length !== approvals.length) {
97
+ const updates = {
98
+ pendingApprovals: filtered.length > 0 ? JSON.stringify(filtered) : null,
99
+ updatedAt: sql `CURRENT_TIMESTAMP`,
100
+ };
101
+ if (filtered.length === 0) {
102
+ updates.status = status;
103
+ }
104
+ // Compare-and-swap on the list we read: a concurrent approver that got
105
+ // there first has already rewritten it, and this update matches no row.
106
+ const result = await this.db
107
+ .updateTable('agentRun')
108
+ .set(updates)
109
+ .where('runId', '=', row.runId)
110
+ .where('status', '=', 'suspended')
111
+ .where('pendingApprovals', '=', row.pendingApprovals)
112
+ .executeTakeFirst();
113
+ return Number(result?.numUpdatedRows ?? 0) > 0;
114
+ }
115
+ }
116
+ return false;
117
+ }
118
+ async findRunByToolCallId(toolCallId) {
119
+ const rows = await this.db
120
+ .selectFrom('agentRun')
121
+ .selectAll()
122
+ .where('status', '=', 'suspended')
123
+ .execute();
124
+ for (const row of rows) {
125
+ let approvals = [];
126
+ if (row.pendingApprovals) {
127
+ try {
128
+ approvals = JSON.parse(row.pendingApprovals);
129
+ }
130
+ catch {
131
+ console.warn(`Failed to parse pendingApprovals for run ${row.runId}, treating as empty`);
132
+ }
133
+ }
134
+ const approval = approvals.find((a) => a.toolCallId === toolCallId);
135
+ if (approval) {
136
+ return { run: this.toRunState(row), approval };
137
+ }
138
+ }
139
+ return null;
140
+ }
141
+ async saveScore(score) {
142
+ await saveRunScore(this.db, score);
143
+ }
144
+ async getScores(runId) {
145
+ return getRunScores(this.db, runId);
146
+ }
147
+ toRunState(row) {
148
+ return {
149
+ runId: row.runId,
150
+ agentName: row.agentName,
151
+ threadId: row.threadId,
152
+ resourceId: row.resourceId,
153
+ status: row.status,
154
+ errorMessage: row.errorMessage ?? undefined,
155
+ suspendReason: row.suspendReason ?? undefined,
156
+ missingRpcs: row.missingRpcs ? JSON.parse(row.missingRpcs) : undefined,
157
+ pendingApprovals: row.pendingApprovals
158
+ ? JSON.parse(row.pendingApprovals)
159
+ : undefined,
160
+ usage: {
161
+ inputTokens: row.usageInputTokens ?? 0,
162
+ outputTokens: row.usageOutputTokens ?? 0,
163
+ model: row.usageModel ?? '',
164
+ },
165
+ createdAt: new Date(row.createdAt),
166
+ updatedAt: new Date(row.updatedAt),
167
+ };
168
+ }
169
+ }
@@ -0,0 +1,40 @@
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 { Kysely } from 'kysely';
5
+ import type { KyselyPikkuDB } from './kysely-tables.js';
6
+ export declare class KyselyAgentStorageService implements AgentStorageService, AgentRunStateService {
7
+ private db;
8
+ private initialized;
9
+ constructor(db: Kysely<KyselyPikkuDB>);
10
+ init(): Promise<void>;
11
+ createThread(resourceId: string, options?: {
12
+ threadId?: string;
13
+ title?: string;
14
+ metadata?: Record<string, unknown>;
15
+ }): Promise<AgentThread>;
16
+ getThread(threadId: string): Promise<AgentThread>;
17
+ getThreads(resourceId: string): Promise<AgentThread[]>;
18
+ deleteThread(threadId: string): Promise<void>;
19
+ getMessages(threadId: string, options?: {
20
+ lastN?: number;
21
+ cursor?: string;
22
+ }): Promise<AgentMessage[]>;
23
+ saveMessages(threadId: string, messages: AgentMessage[]): Promise<void>;
24
+ getWorkingMemory(id: string, scope: 'resource' | 'thread'): Promise<Record<string, unknown> | null>;
25
+ saveWorkingMemory(id: string, scope: 'resource' | 'thread', data: Record<string, unknown>): Promise<void>;
26
+ createRun(run: CreateRunInput): Promise<string>;
27
+ updateRun(runId: string, updates: Partial<AgentRunState>): Promise<void>;
28
+ getRun(runId: string): Promise<AgentRunState | null>;
29
+ getRunsByThread(threadId: string): Promise<AgentRunState[]>;
30
+ private setApprovals;
31
+ private mapRunRow;
32
+ findRunByToolCallId(toolCallId: string): Promise<{
33
+ run: AgentRunState;
34
+ approval: NonNullable<AgentRunState['pendingApprovals']>[number];
35
+ } | null>;
36
+ resolveApproval(toolCallId: string, status: 'approved' | 'denied'): Promise<boolean>;
37
+ saveScore(score: SaveScoreInput): Promise<void>;
38
+ getScores(runId: string): Promise<AgentRunScore[]>;
39
+ close(): Promise<void>;
40
+ }