@pikku/kysely 0.13.12 → 0.13.15
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 +78 -0
- package/dist/src/index.d.ts +2 -5
- package/dist/src/index.js +2 -3
- package/dist/src/kysely-ai-run-scores.d.ts +10 -0
- package/dist/src/kysely-ai-run-scores.js +34 -0
- package/dist/src/kysely-ai-run-state-service.d.ts +5 -2
- package/dist/src/kysely-ai-run-state-service.js +15 -3
- package/dist/src/kysely-ai-storage-service.d.ts +5 -2
- package/dist/src/kysely-ai-storage-service.js +12 -2
- package/dist/src/kysely-tables.d.ts +12 -0
- package/dist/src/kysely-workflow-service.js +5 -0
- package/dist/src/schema/ai.schema.js +13 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/index.ts +0 -15
- package/src/kysely-ai-run-scores.ts +47 -0
- package/src/kysely-ai-run-state-service.ts +24 -5
- package/src/kysely-ai-storage-service.ts +17 -3
- package/src/kysely-services.test.ts +1 -0
- package/src/kysely-tables.ts +13 -0
- package/src/kysely-webhook-service.test.ts +1 -1
- package/src/kysely-workflow-service.ts +5 -0
- package/src/schema/ai.schema.ts +21 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,81 @@
|
|
|
1
|
+
## 0.13.15
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- 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.
|
|
6
|
+
- Updated dependencies [063f43a]
|
|
7
|
+
- Updated dependencies [ce66bf8]
|
|
8
|
+
- Updated dependencies [d0307a8]
|
|
9
|
+
- Updated dependencies [ce66bf8]
|
|
10
|
+
- Updated dependencies [3ad2131]
|
|
11
|
+
- Updated dependencies [b930dca]
|
|
12
|
+
- Updated dependencies [b95e77d]
|
|
13
|
+
- Updated dependencies [fd9d834]
|
|
14
|
+
- Updated dependencies [8978fbd]
|
|
15
|
+
- @pikku/core@0.12.82
|
|
16
|
+
|
|
17
|
+
## 0.13.14
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- e110c55: Add runtime scoring for AI agents: `pikkuAIScorer` for heuristic grades and
|
|
22
|
+
`pikkuAIJudge` for LLM-judged ones, graded off the request path on two queue
|
|
23
|
+
lanes so a slow judge cannot starve the cheap checks. Grades are sampled
|
|
24
|
+
deterministically per `(run, scorer)` and persisted to `ai_run_score`.
|
|
25
|
+
- Updated dependencies [e110c55]
|
|
26
|
+
- Updated dependencies [e110c55]
|
|
27
|
+
- Updated dependencies [e110c55]
|
|
28
|
+
- Updated dependencies [acc8077]
|
|
29
|
+
- Updated dependencies [905f737]
|
|
30
|
+
- Updated dependencies [3cc6428]
|
|
31
|
+
- Updated dependencies [c524adf]
|
|
32
|
+
- Updated dependencies [e110c55]
|
|
33
|
+
- @pikku/core@0.12.81
|
|
34
|
+
|
|
35
|
+
## 0.13.13
|
|
36
|
+
|
|
37
|
+
### Patch Changes
|
|
38
|
+
|
|
39
|
+
- 6dada45: fix(workflow,ai-agent): make a run's owner, entry node and step function authoritative
|
|
40
|
+
|
|
41
|
+
A graph run may only start at a node the graph declared in `meta.entryNodeIds`, and
|
|
42
|
+
the generated `POST /workflow/:workflowName/graph/:nodeId` route that let an HTTP
|
|
43
|
+
caller pick the entry node is gone. `startNode` stays for `PikkuTriggerService`,
|
|
44
|
+
which names a declared entry node anyway.
|
|
45
|
+
|
|
46
|
+
`StepState` now records the `rpcName` the workflow dispatched a step with, and the
|
|
47
|
+
step claim rejects a queue message naming a different function with
|
|
48
|
+
`WorkflowStepFunctionMismatchError` before mutating any status — a step runs under
|
|
49
|
+
the run owner's identity and without the `expose` gate, so the message must not
|
|
50
|
+
choose what runs.
|
|
51
|
+
|
|
52
|
+
`approveStep` takes the caller's session, and the generated status routes and
|
|
53
|
+
streams assert the same `assertWorkflowRunOwner` check: a run started through a
|
|
54
|
+
session may only be read and approved by that session's user. A run with no
|
|
55
|
+
recorded owner (trigger, scheduler, unauthenticated route) has nobody to compare
|
|
56
|
+
against and is still gated by the entrypoint's own `auth`/`permissions`.
|
|
57
|
+
|
|
58
|
+
`AIRunStateService.resolveApproval` is now a compare-and-swap returning whether
|
|
59
|
+
_this_ caller made the claim, and both agent resume paths run a tool only for the
|
|
60
|
+
approvals they claimed — concurrent approvals of one tool call no longer all
|
|
61
|
+
execute it.
|
|
62
|
+
|
|
63
|
+
- Updated dependencies [41c1a95]
|
|
64
|
+
- Updated dependencies [ce96383]
|
|
65
|
+
- Updated dependencies [7e60867]
|
|
66
|
+
- Updated dependencies [f8f1244]
|
|
67
|
+
- Updated dependencies [dcf20cb]
|
|
68
|
+
- Updated dependencies [6512384]
|
|
69
|
+
- Updated dependencies [e3b4c14]
|
|
70
|
+
- Updated dependencies [efd0ed1]
|
|
71
|
+
- Updated dependencies [cba98fb]
|
|
72
|
+
- Updated dependencies [ce96383]
|
|
73
|
+
- Updated dependencies [f8f1244]
|
|
74
|
+
- Updated dependencies [f8f1244]
|
|
75
|
+
- Updated dependencies [6e93a35]
|
|
76
|
+
- Updated dependencies [6dada45]
|
|
77
|
+
- @pikku/core@0.12.80
|
|
78
|
+
|
|
1
79
|
## 0.13.12
|
|
2
80
|
|
|
3
81
|
### Patch Changes
|
package/dist/src/index.d.ts
CHANGED
|
@@ -8,18 +8,15 @@ export { KyselyAIStorageService } from './kysely-ai-storage-service.js';
|
|
|
8
8
|
export { KyselyAgentRunService } from './kysely-ai-agent-run-service.js';
|
|
9
9
|
export { KyselyAIRunStateService } from './kysely-ai-run-state-service.js';
|
|
10
10
|
export { KyselySecretService } from './kysely-secret-service.js';
|
|
11
|
-
export type { KyselySecretServiceConfig } from './kysely-secret-service.js';
|
|
12
11
|
export { KyselyCredentialService } from './kysely-credential-service.js';
|
|
13
|
-
export type { KyselyCredentialServiceConfig } from './kysely-credential-service.js';
|
|
14
12
|
export { KyselySessionStore } from './kysely-session-store.js';
|
|
15
13
|
export { KyselyScopeService } from './kysely-scope-service.js';
|
|
16
14
|
export { KyselyWebhookService } from './kysely-webhook-service.js';
|
|
17
15
|
export { createAuditedKysely, type CreateAuditedKyselyOptions, } from './create-audited-kysely.js';
|
|
18
16
|
export { KyselyAuditService } from './kysely-audit-service.js';
|
|
19
17
|
export { KyselyVirtualUserRunStore } from './kysely-virtual-user-run-store.js';
|
|
20
|
-
export { SerializePlugin, SqliteSerializePlugin, BaseSerializePlugin,
|
|
21
|
-
export {
|
|
22
|
-
export { auditSchema, pikkuSchemas, applyPikkuSchemas, compilePikkuSchemas, ensurePikkuSchema, type EnsureOutcome, resolveRequirements, unsatisfiedRequirements, rawStatement, requiredType, type PikkuSchema, type RequiredTypes, type SchemaRequirement, type UnmetRequirement, type SchemaStatement, type SchemaStatementFactory, } from './schema/index.js';
|
|
18
|
+
export { SerializePlugin, SqliteSerializePlugin, BaseSerializePlugin, type Serializer, type Deserializer, } from './serialize-plugin.js';
|
|
19
|
+
export { auditSchema, pikkuSchemas, applyPikkuSchemas, compilePikkuSchemas, ensurePikkuSchema, type EnsureOutcome, resolveRequirements, type PikkuSchema, type RequiredTypes, type UnmetRequirement, type SchemaStatementFactory, } from './schema/index.js';
|
|
23
20
|
export type { KyselyPikkuDB } from './kysely-tables.js';
|
|
24
21
|
export type { WorkflowRunService } from '@pikku/core/workflow';
|
|
25
22
|
export type { AgentRunService, AgentRunRow } from '@pikku/core/ai-agent';
|
package/dist/src/index.js
CHANGED
|
@@ -15,6 +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,
|
|
19
|
-
export {
|
|
20
|
-
export { auditSchema, pikkuSchemas, applyPikkuSchemas, compilePikkuSchemas, ensurePikkuSchema, resolveRequirements, unsatisfiedRequirements, rawStatement, requiredType, } from './schema/index.js';
|
|
18
|
+
export { SerializePlugin, SqliteSerializePlugin, BaseSerializePlugin, } from './serialize-plugin.js';
|
|
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 { AIRunScore } from '@pikku/core/ai-scorer';
|
|
5
|
+
/**
|
|
6
|
+
* Shared by the two Kysely services that implement `AIRunStateService` — 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<AIRunScore[]>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { parseJson } from './kysely-json.js';
|
|
2
|
+
/**
|
|
3
|
+
* Shared by the two Kysely services that implement `AIRunStateService` — 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('aiRunScore')
|
|
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('aiRunScore')
|
|
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
|
+
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Kysely } from 'kysely';
|
|
2
2
|
import type { KyselyPikkuDB } from './kysely-tables.js';
|
|
3
|
-
import type { AIRunStateService, CreateRunInput } from '@pikku/core/services';
|
|
3
|
+
import type { AIRunStateService, CreateRunInput, SaveScoreInput } from '@pikku/core/services';
|
|
4
4
|
import type { AgentRunState, PendingApproval } from '@pikku/core/ai-agent';
|
|
5
|
+
import type { AIRunScore } from '@pikku/core/ai-scorer';
|
|
5
6
|
export declare class KyselyAIRunStateService implements AIRunStateService {
|
|
6
7
|
private db;
|
|
7
8
|
private initialized;
|
|
@@ -11,10 +12,12 @@ export declare class KyselyAIRunStateService implements AIRunStateService {
|
|
|
11
12
|
updateRun(runId: string, updates: Partial<AgentRunState>): Promise<void>;
|
|
12
13
|
getRun(runId: string): Promise<AgentRunState | null>;
|
|
13
14
|
getRunsByThread(threadId: string): Promise<AgentRunState[]>;
|
|
14
|
-
resolveApproval(toolCallId: string, status: 'approved' | 'denied'): Promise<
|
|
15
|
+
resolveApproval(toolCallId: string, status: 'approved' | 'denied'): Promise<boolean>;
|
|
15
16
|
findRunByToolCallId(toolCallId: string): Promise<{
|
|
16
17
|
run: AgentRunState;
|
|
17
18
|
approval: PendingApproval;
|
|
18
19
|
} | null>;
|
|
20
|
+
saveScore(score: SaveScoreInput): Promise<void>;
|
|
21
|
+
getScores(runId: string): Promise<AIRunScore[]>;
|
|
19
22
|
private toRunState;
|
|
20
23
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { sql } from 'kysely';
|
|
2
2
|
import { ensurePikkuSchema } from './schema/index.js';
|
|
3
3
|
import { aiSchema } from './schema/ai.schema.js';
|
|
4
|
+
import { getRunScores, saveRunScore } from './kysely-ai-run-scores.js';
|
|
4
5
|
export class KyselyAIRunStateService {
|
|
5
6
|
db;
|
|
6
7
|
initialized = false;
|
|
@@ -100,14 +101,19 @@ export class KyselyAIRunStateService {
|
|
|
100
101
|
if (filtered.length === 0) {
|
|
101
102
|
updates.status = status;
|
|
102
103
|
}
|
|
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
|
|
104
107
|
.updateTable('aiRun')
|
|
105
108
|
.set(updates)
|
|
106
109
|
.where('runId', '=', row.runId)
|
|
107
|
-
.
|
|
108
|
-
|
|
110
|
+
.where('status', '=', 'suspended')
|
|
111
|
+
.where('pendingApprovals', '=', row.pendingApprovals)
|
|
112
|
+
.executeTakeFirst();
|
|
113
|
+
return Number(result?.numUpdatedRows ?? 0) > 0;
|
|
109
114
|
}
|
|
110
115
|
}
|
|
116
|
+
return false;
|
|
111
117
|
}
|
|
112
118
|
async findRunByToolCallId(toolCallId) {
|
|
113
119
|
const rows = await this.db
|
|
@@ -132,6 +138,12 @@ export class KyselyAIRunStateService {
|
|
|
132
138
|
}
|
|
133
139
|
return null;
|
|
134
140
|
}
|
|
141
|
+
async saveScore(score) {
|
|
142
|
+
await saveRunScore(this.db, score);
|
|
143
|
+
}
|
|
144
|
+
async getScores(runId) {
|
|
145
|
+
return getRunScores(this.db, runId);
|
|
146
|
+
}
|
|
135
147
|
toRunState(row) {
|
|
136
148
|
return {
|
|
137
149
|
runId: row.runId,
|
|
@@ -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 { Kysely } from 'kysely';
|
|
4
5
|
import type { KyselyPikkuDB } from './kysely-tables.js';
|
|
5
6
|
export declare class KyselyAIStorageService implements AIStorageService, AIRunStateService {
|
|
@@ -32,6 +33,8 @@ export declare class KyselyAIStorageService implements AIStorageService, AIRunSt
|
|
|
32
33
|
run: AgentRunState;
|
|
33
34
|
approval: NonNullable<AgentRunState['pendingApprovals']>[number];
|
|
34
35
|
} | null>;
|
|
35
|
-
resolveApproval(toolCallId: string, status: 'approved' | 'denied'): Promise<
|
|
36
|
+
resolveApproval(toolCallId: string, status: 'approved' | 'denied'): Promise<boolean>;
|
|
37
|
+
saveScore(score: SaveScoreInput): Promise<void>;
|
|
38
|
+
getScores(runId: string): Promise<AIRunScore[]>;
|
|
36
39
|
close(): Promise<void>;
|
|
37
40
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { parseJson } from './kysely-json.js';
|
|
2
|
+
import { getRunScores, saveRunScore } from './kysely-ai-run-scores.js';
|
|
2
3
|
import { ensurePikkuSchema } from './schema/index.js';
|
|
3
4
|
import { aiSchema } from './schema/ai.schema.js';
|
|
4
5
|
export class KyselyAIStorageService {
|
|
@@ -510,11 +511,20 @@ export class KyselyAIStorageService {
|
|
|
510
511
|
return { run, approval };
|
|
511
512
|
}
|
|
512
513
|
async resolveApproval(toolCallId, status) {
|
|
513
|
-
|
|
514
|
+
// Only the caller that moves the tool call off 'pending' has claimed it.
|
|
515
|
+
const result = await this.db
|
|
514
516
|
.updateTable('aiToolCall')
|
|
515
517
|
.set({ approvalStatus: status })
|
|
516
518
|
.where('id', '=', toolCallId)
|
|
517
|
-
.
|
|
519
|
+
.where('approvalStatus', '=', 'pending')
|
|
520
|
+
.executeTakeFirst();
|
|
521
|
+
return Number(result?.numUpdatedRows ?? 0) > 0;
|
|
522
|
+
}
|
|
523
|
+
async saveScore(score) {
|
|
524
|
+
await saveRunScore(this.db, score);
|
|
525
|
+
}
|
|
526
|
+
async getScores(runId) {
|
|
527
|
+
return getRunScores(this.db, runId);
|
|
518
528
|
}
|
|
519
529
|
async close() { }
|
|
520
530
|
}
|
|
@@ -133,6 +133,17 @@ export interface AIRunTable {
|
|
|
133
133
|
createdAt: Generated<Date>;
|
|
134
134
|
updatedAt: Generated<Date>;
|
|
135
135
|
}
|
|
136
|
+
export interface AIRunScoreTable {
|
|
137
|
+
id: string;
|
|
138
|
+
runId: string;
|
|
139
|
+
scorerName: string;
|
|
140
|
+
/** 0..1, so grades are comparable across scorers. */
|
|
141
|
+
score: number;
|
|
142
|
+
reason: string | null;
|
|
143
|
+
/** JSON-serialized structured detail the scorer returned alongside the score. */
|
|
144
|
+
metadata: string | null;
|
|
145
|
+
createdAt: Generated<Date>;
|
|
146
|
+
}
|
|
136
147
|
export interface PikkuDeploymentsTable {
|
|
137
148
|
deploymentId: string;
|
|
138
149
|
endpoint: string;
|
|
@@ -295,6 +306,7 @@ export interface KyselyPikkuDB {
|
|
|
295
306
|
aiToolCall: AIToolCallTable;
|
|
296
307
|
aiWorkingMemory: AIWorkingMemoryTable;
|
|
297
308
|
aiRun: AIRunTable;
|
|
309
|
+
aiRunScore: AIRunScoreTable;
|
|
298
310
|
pikkuDeployments: PikkuDeploymentsTable;
|
|
299
311
|
pikkuDeploymentFunctions: PikkuDeploymentFunctionsTable;
|
|
300
312
|
secrets: SecretsTable;
|
|
@@ -78,6 +78,7 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
|
|
|
78
78
|
return {
|
|
79
79
|
stepId,
|
|
80
80
|
status: 'pending',
|
|
81
|
+
rpcName,
|
|
81
82
|
result: undefined,
|
|
82
83
|
error: undefined,
|
|
83
84
|
attemptCount: 1,
|
|
@@ -94,6 +95,7 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
|
|
|
94
95
|
.select([
|
|
95
96
|
'workflowStepId',
|
|
96
97
|
'status',
|
|
98
|
+
'rpcName',
|
|
97
99
|
'result',
|
|
98
100
|
'error',
|
|
99
101
|
'retries',
|
|
@@ -115,6 +117,7 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
|
|
|
115
117
|
return {
|
|
116
118
|
stepId: row.workflowStepId,
|
|
117
119
|
status: row.status,
|
|
120
|
+
rpcName: row.rpcName ?? null,
|
|
118
121
|
result: parseJson(row.result),
|
|
119
122
|
error: parseJson(row.error),
|
|
120
123
|
attemptCount: Number(row.currentAttempt ?? 1),
|
|
@@ -331,6 +334,7 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
|
|
|
331
334
|
.select([
|
|
332
335
|
'workflowStepId',
|
|
333
336
|
'status',
|
|
337
|
+
'rpcName',
|
|
334
338
|
'result',
|
|
335
339
|
'error',
|
|
336
340
|
'retries',
|
|
@@ -345,6 +349,7 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
|
|
|
345
349
|
return {
|
|
346
350
|
stepId: row.workflowStepId,
|
|
347
351
|
status: row.status,
|
|
352
|
+
rpcName: row.rpcName ?? null,
|
|
348
353
|
result: parseJson(row.result),
|
|
349
354
|
error: parseJson(row.error),
|
|
350
355
|
attemptCount: Number(row.currentAttempt ?? 1),
|
|
@@ -85,5 +85,18 @@ export const aiSchema = {
|
|
|
85
85
|
.createIndex('idx_ai_run_thread')
|
|
86
86
|
.on('aiRun')
|
|
87
87
|
.columns(['threadId', 'createdAt']),
|
|
88
|
+
(db) => db.schema
|
|
89
|
+
.createTable('aiRunScore')
|
|
90
|
+
.addColumn('id', 'varchar(36)', (col) => col.primaryKey())
|
|
91
|
+
.addColumn('runId', 'varchar(36)', (col) => col.notNull().references('aiRun.runId').onDelete('cascade'))
|
|
92
|
+
.addColumn('scorerName', 'varchar(255)', (col) => col.notNull())
|
|
93
|
+
.addColumn('score', 'real', (col) => col.notNull())
|
|
94
|
+
.addColumn('reason', 'text')
|
|
95
|
+
.addColumn('metadata', 'text')
|
|
96
|
+
.addColumn('createdAt', 'timestamp', (col) => col.defaultTo(sql `CURRENT_TIMESTAMP`).notNull()),
|
|
97
|
+
(db) => db.schema
|
|
98
|
+
.createIndex('idx_ai_run_score_run')
|
|
99
|
+
.on('aiRunScore')
|
|
100
|
+
.columns(['runId', 'createdAt']),
|
|
88
101
|
],
|
|
89
102
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/create-audited-kysely.ts","../src/index.ts","../src/kysely-ai-agent-run-service.ts","../src/kysely-ai-run-state-service.ts","../src/kysely-ai-storage-service.ts","../src/kysely-audit-service.ts","../src/kysely-channel-store.ts","../src/kysely-credential-service.ts","../src/kysely-deployment-service.ts","../src/kysely-eventhub-store.ts","../src/kysely-json.ts","../src/kysely-scope-service.ts","../src/kysely-secret-service.ts","../src/kysely-session-store.ts","../src/kysely-tables.ts","../src/kysely-virtual-user-run-store.ts","../src/kysely-webhook-service.ts","../src/kysely-workflow-mirror.ts","../src/kysely-workflow-run-service.ts","../src/kysely-workflow-service.ts","../src/serialize-plugin.ts","../src/schema/ai.schema.ts","../src/schema/audit.schema.ts","../src/schema/channel.schema.ts","../src/schema/credential.schema.ts","../src/schema/deployment.schema.ts","../src/schema/index.ts","../src/schema/pikku-schema.types.ts","../src/schema/scope.schema.ts","../src/schema/secret.schema.ts","../src/schema/session.schema.ts","../src/schema/virtual-user.schema.ts","../src/schema/webhook.schema.ts","../src/schema/workflow.schema.ts","../bin/pikku-kysely-pure.ts"],"version":"6.0.3"}
|
|
1
|
+
{"root":["../src/create-audited-kysely.ts","../src/index.ts","../src/kysely-ai-agent-run-service.ts","../src/kysely-ai-run-scores.ts","../src/kysely-ai-run-state-service.ts","../src/kysely-ai-storage-service.ts","../src/kysely-audit-service.ts","../src/kysely-channel-store.ts","../src/kysely-credential-service.ts","../src/kysely-deployment-service.ts","../src/kysely-eventhub-store.ts","../src/kysely-json.ts","../src/kysely-scope-service.ts","../src/kysely-secret-service.ts","../src/kysely-session-store.ts","../src/kysely-tables.ts","../src/kysely-virtual-user-run-store.ts","../src/kysely-webhook-service.ts","../src/kysely-workflow-mirror.ts","../src/kysely-workflow-run-service.ts","../src/kysely-workflow-service.ts","../src/serialize-plugin.ts","../src/schema/ai.schema.ts","../src/schema/audit.schema.ts","../src/schema/channel.schema.ts","../src/schema/credential.schema.ts","../src/schema/deployment.schema.ts","../src/schema/index.ts","../src/schema/pikku-schema.types.ts","../src/schema/scope.schema.ts","../src/schema/secret.schema.ts","../src/schema/session.schema.ts","../src/schema/virtual-user.schema.ts","../src/schema/webhook.schema.ts","../src/schema/workflow.schema.ts","../bin/pikku-kysely-pure.ts"],"version":"6.0.3"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/kysely",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.15",
|
|
4
4
|
"description": "Kysely-backed Pikku services — workflow state, secrets, sessions, scopes and audit",
|
|
5
5
|
"author": "yasser.fadl@gmail.com",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"prepublishOnly": "yarn build"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
|
-
"@pikku/core": "^0.12.
|
|
24
|
+
"@pikku/core": "^0.12.82"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"kysely": "^0.29.0"
|
package/src/index.ts
CHANGED
|
@@ -8,9 +8,7 @@ export { KyselyAIStorageService } from './kysely-ai-storage-service.js'
|
|
|
8
8
|
export { KyselyAgentRunService } from './kysely-ai-agent-run-service.js'
|
|
9
9
|
export { KyselyAIRunStateService } from './kysely-ai-run-state-service.js'
|
|
10
10
|
export { KyselySecretService } from './kysely-secret-service.js'
|
|
11
|
-
export type { KyselySecretServiceConfig } from './kysely-secret-service.js'
|
|
12
11
|
export { KyselyCredentialService } from './kysely-credential-service.js'
|
|
13
|
-
export type { KyselyCredentialServiceConfig } from './kysely-credential-service.js'
|
|
14
12
|
export { KyselySessionStore } from './kysely-session-store.js'
|
|
15
13
|
export { KyselyScopeService } from './kysely-scope-service.js'
|
|
16
14
|
export { KyselyWebhookService } from './kysely-webhook-service.js'
|
|
@@ -25,17 +23,9 @@ export {
|
|
|
25
23
|
SerializePlugin,
|
|
26
24
|
SqliteSerializePlugin,
|
|
27
25
|
BaseSerializePlugin,
|
|
28
|
-
defaultSerializer,
|
|
29
|
-
defaultDeserializer,
|
|
30
|
-
maybeJson,
|
|
31
|
-
skipTransform,
|
|
32
|
-
dateRegex,
|
|
33
26
|
type Serializer,
|
|
34
27
|
type Deserializer,
|
|
35
|
-
type SerializePluginOptions,
|
|
36
28
|
} from './serialize-plugin.js'
|
|
37
|
-
|
|
38
|
-
export { virtualUserSchema } from './schema/virtual-user.schema.js'
|
|
39
29
|
export {
|
|
40
30
|
auditSchema,
|
|
41
31
|
pikkuSchemas,
|
|
@@ -44,14 +34,9 @@ export {
|
|
|
44
34
|
ensurePikkuSchema,
|
|
45
35
|
type EnsureOutcome,
|
|
46
36
|
resolveRequirements,
|
|
47
|
-
unsatisfiedRequirements,
|
|
48
|
-
rawStatement,
|
|
49
|
-
requiredType,
|
|
50
37
|
type PikkuSchema,
|
|
51
38
|
type RequiredTypes,
|
|
52
|
-
type SchemaRequirement,
|
|
53
39
|
type UnmetRequirement,
|
|
54
|
-
type SchemaStatement,
|
|
55
40
|
type SchemaStatementFactory,
|
|
56
41
|
} from './schema/index.js'
|
|
57
42
|
|
|
@@ -0,0 +1,47 @@
|
|
|
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 { AIRunScore } from '@pikku/core/ai-scorer'
|
|
5
|
+
import { parseJson } from './kysely-json.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Shared by the two Kysely services that implement `AIRunStateService` — they
|
|
9
|
+
* differ in how they store approvals, not in how they store grades.
|
|
10
|
+
*/
|
|
11
|
+
export const saveRunScore = async (
|
|
12
|
+
db: Kysely<KyselyPikkuDB>,
|
|
13
|
+
score: SaveScoreInput
|
|
14
|
+
): Promise<void> => {
|
|
15
|
+
await db
|
|
16
|
+
.insertInto('aiRunScore')
|
|
17
|
+
.values({
|
|
18
|
+
id: crypto.randomUUID(),
|
|
19
|
+
runId: score.runId,
|
|
20
|
+
scorerName: score.scorerName,
|
|
21
|
+
score: score.score,
|
|
22
|
+
reason: score.reason ?? null,
|
|
23
|
+
metadata: score.metadata ? JSON.stringify(score.metadata) : null,
|
|
24
|
+
})
|
|
25
|
+
.execute()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const getRunScores = async (
|
|
29
|
+
db: Kysely<KyselyPikkuDB>,
|
|
30
|
+
runId: string
|
|
31
|
+
): Promise<AIRunScore[]> => {
|
|
32
|
+
const rows = await db
|
|
33
|
+
.selectFrom('aiRunScore')
|
|
34
|
+
.selectAll()
|
|
35
|
+
.where('runId', '=', runId)
|
|
36
|
+
.orderBy('createdAt', 'asc')
|
|
37
|
+
.execute()
|
|
38
|
+
|
|
39
|
+
return rows.map((row) => ({
|
|
40
|
+
runId: row.runId,
|
|
41
|
+
scorerName: row.scorerName,
|
|
42
|
+
score: Number(row.score),
|
|
43
|
+
reason: row.reason ?? undefined,
|
|
44
|
+
metadata: parseJson(row.metadata),
|
|
45
|
+
createdAt: new Date(row.createdAt),
|
|
46
|
+
}))
|
|
47
|
+
}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { sql } from 'kysely'
|
|
2
2
|
import type { Kysely } from 'kysely'
|
|
3
3
|
import type { KyselyPikkuDB } from './kysely-tables.js'
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
AIRunStateService,
|
|
6
|
+
CreateRunInput,
|
|
7
|
+
SaveScoreInput,
|
|
8
|
+
} from '@pikku/core/services'
|
|
5
9
|
import type { AgentRunState, PendingApproval } from '@pikku/core/ai-agent'
|
|
10
|
+
import type { AIRunScore } from '@pikku/core/ai-scorer'
|
|
6
11
|
import { ensurePikkuSchema } from './schema/index.js'
|
|
7
12
|
import { aiSchema } from './schema/ai.schema.js'
|
|
13
|
+
import { getRunScores, saveRunScore } from './kysely-ai-run-scores.js'
|
|
8
14
|
|
|
9
15
|
export class KyselyAIRunStateService implements AIRunStateService {
|
|
10
16
|
private initialized = false
|
|
@@ -89,7 +95,7 @@ export class KyselyAIRunStateService implements AIRunStateService {
|
|
|
89
95
|
async resolveApproval(
|
|
90
96
|
toolCallId: string,
|
|
91
97
|
status: 'approved' | 'denied'
|
|
92
|
-
): Promise<
|
|
98
|
+
): Promise<boolean> {
|
|
93
99
|
const rows = await this.db
|
|
94
100
|
.selectFrom('aiRun')
|
|
95
101
|
.select(['runId', 'pendingApprovals'])
|
|
@@ -117,14 +123,19 @@ export class KyselyAIRunStateService implements AIRunStateService {
|
|
|
117
123
|
if (filtered.length === 0) {
|
|
118
124
|
updates.status = status
|
|
119
125
|
}
|
|
120
|
-
|
|
126
|
+
// Compare-and-swap on the list we read: a concurrent approver that got
|
|
127
|
+
// there first has already rewritten it, and this update matches no row.
|
|
128
|
+
const result = await this.db
|
|
121
129
|
.updateTable('aiRun')
|
|
122
130
|
.set(updates as any)
|
|
123
131
|
.where('runId', '=', row.runId)
|
|
124
|
-
.
|
|
125
|
-
|
|
132
|
+
.where('status', '=', 'suspended')
|
|
133
|
+
.where('pendingApprovals', '=', row.pendingApprovals)
|
|
134
|
+
.executeTakeFirst()
|
|
135
|
+
return Number(result?.numUpdatedRows ?? 0) > 0
|
|
126
136
|
}
|
|
127
137
|
}
|
|
138
|
+
return false
|
|
128
139
|
}
|
|
129
140
|
|
|
130
141
|
async findRunByToolCallId(
|
|
@@ -155,6 +166,14 @@ export class KyselyAIRunStateService implements AIRunStateService {
|
|
|
155
166
|
return null
|
|
156
167
|
}
|
|
157
168
|
|
|
169
|
+
async saveScore(score: SaveScoreInput): Promise<void> {
|
|
170
|
+
await saveRunScore(this.db, score)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async getScores(runId: string): Promise<AIRunScore[]> {
|
|
174
|
+
return getRunScores(this.db, runId)
|
|
175
|
+
}
|
|
176
|
+
|
|
158
177
|
private toRunState(row: any): AgentRunState {
|
|
159
178
|
return {
|
|
160
179
|
runId: row.runId,
|
|
@@ -2,11 +2,14 @@ import type {
|
|
|
2
2
|
AIStorageService,
|
|
3
3
|
AIRunStateService,
|
|
4
4
|
CreateRunInput,
|
|
5
|
+
SaveScoreInput,
|
|
5
6
|
} from '@pikku/core/services'
|
|
6
7
|
import type { AIThread, AIMessage, AgentRunState } from '@pikku/core/ai-agent'
|
|
8
|
+
import type { AIRunScore } from '@pikku/core/ai-scorer'
|
|
7
9
|
import type { Kysely } from 'kysely'
|
|
8
10
|
import type { KyselyPikkuDB } from './kysely-tables.js'
|
|
9
11
|
import { parseJson } from './kysely-json.js'
|
|
12
|
+
import { getRunScores, saveRunScore } from './kysely-ai-run-scores.js'
|
|
10
13
|
import { ensurePikkuSchema } from './schema/index.js'
|
|
11
14
|
import { aiSchema } from './schema/ai.schema.js'
|
|
12
15
|
|
|
@@ -596,12 +599,23 @@ export class KyselyAIStorageService
|
|
|
596
599
|
async resolveApproval(
|
|
597
600
|
toolCallId: string,
|
|
598
601
|
status: 'approved' | 'denied'
|
|
599
|
-
): Promise<
|
|
600
|
-
|
|
602
|
+
): Promise<boolean> {
|
|
603
|
+
// Only the caller that moves the tool call off 'pending' has claimed it.
|
|
604
|
+
const result = await this.db
|
|
601
605
|
.updateTable('aiToolCall')
|
|
602
606
|
.set({ approvalStatus: status })
|
|
603
607
|
.where('id', '=', toolCallId)
|
|
604
|
-
.
|
|
608
|
+
.where('approvalStatus', '=', 'pending')
|
|
609
|
+
.executeTakeFirst()
|
|
610
|
+
return Number(result?.numUpdatedRows ?? 0) > 0
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
async saveScore(score: SaveScoreInput): Promise<void> {
|
|
614
|
+
await saveRunScore(this.db, score)
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
async getScores(runId: string): Promise<AIRunScore[]> {
|
|
618
|
+
return getRunScores(this.db, runId)
|
|
605
619
|
}
|
|
606
620
|
|
|
607
621
|
public async close(): Promise<void> {}
|
package/src/kysely-tables.ts
CHANGED
|
@@ -151,6 +151,18 @@ export interface AIRunTable {
|
|
|
151
151
|
updatedAt: Generated<Date>
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
export interface AIRunScoreTable {
|
|
155
|
+
id: string
|
|
156
|
+
runId: string
|
|
157
|
+
scorerName: string
|
|
158
|
+
/** 0..1, so grades are comparable across scorers. */
|
|
159
|
+
score: number
|
|
160
|
+
reason: string | null
|
|
161
|
+
/** JSON-serialized structured detail the scorer returned alongside the score. */
|
|
162
|
+
metadata: string | null
|
|
163
|
+
createdAt: Generated<Date>
|
|
164
|
+
}
|
|
165
|
+
|
|
154
166
|
export interface PikkuDeploymentsTable {
|
|
155
167
|
deploymentId: string
|
|
156
168
|
endpoint: string
|
|
@@ -329,6 +341,7 @@ export interface KyselyPikkuDB {
|
|
|
329
341
|
aiToolCall: AIToolCallTable
|
|
330
342
|
aiWorkingMemory: AIWorkingMemoryTable
|
|
331
343
|
aiRun: AIRunTable
|
|
344
|
+
aiRunScore: AIRunScoreTable
|
|
332
345
|
pikkuDeployments: PikkuDeploymentsTable
|
|
333
346
|
pikkuDeploymentFunctions: PikkuDeploymentFunctionsTable
|
|
334
347
|
secrets: SecretsTable
|
|
@@ -2,7 +2,7 @@ import { describe, test, beforeEach } from 'node:test'
|
|
|
2
2
|
import assert from 'node:assert/strict'
|
|
3
3
|
import { CamelCasePlugin, Kysely, SqliteDialect } from 'kysely'
|
|
4
4
|
import Database from 'better-sqlite3'
|
|
5
|
-
import { setSingletonServices } from '@pikku/core'
|
|
5
|
+
import { setSingletonServices } from '@pikku/core/ecosystem'
|
|
6
6
|
|
|
7
7
|
import type { KyselyPikkuDB } from './kysely-tables.js'
|
|
8
8
|
import { SerializePlugin } from './serialize-plugin.js'
|
|
@@ -123,6 +123,7 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
|
|
|
123
123
|
return {
|
|
124
124
|
stepId,
|
|
125
125
|
status: 'pending',
|
|
126
|
+
rpcName,
|
|
126
127
|
result: undefined,
|
|
127
128
|
error: undefined,
|
|
128
129
|
attemptCount: 1,
|
|
@@ -140,6 +141,7 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
|
|
|
140
141
|
.select([
|
|
141
142
|
'workflowStepId',
|
|
142
143
|
'status',
|
|
144
|
+
'rpcName',
|
|
143
145
|
'result',
|
|
144
146
|
'error',
|
|
145
147
|
'retries',
|
|
@@ -165,6 +167,7 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
|
|
|
165
167
|
return {
|
|
166
168
|
stepId: row.workflowStepId,
|
|
167
169
|
status: row.status as StepState['status'],
|
|
170
|
+
rpcName: row.rpcName ?? null,
|
|
168
171
|
result: parseJson(row.result),
|
|
169
172
|
error: parseJson(row.error),
|
|
170
173
|
attemptCount: Number(row.currentAttempt ?? 1),
|
|
@@ -425,6 +428,7 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
|
|
|
425
428
|
.select([
|
|
426
429
|
'workflowStepId',
|
|
427
430
|
'status',
|
|
431
|
+
'rpcName',
|
|
428
432
|
'result',
|
|
429
433
|
'error',
|
|
430
434
|
'retries',
|
|
@@ -440,6 +444,7 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
|
|
|
440
444
|
return {
|
|
441
445
|
stepId: row.workflowStepId,
|
|
442
446
|
status: row.status as StepState['status'],
|
|
447
|
+
rpcName: row.rpcName ?? null,
|
|
443
448
|
result: parseJson(row.result),
|
|
444
449
|
error: parseJson(row.error),
|
|
445
450
|
attemptCount: Number(row.currentAttempt ?? 1),
|
package/src/schema/ai.schema.ts
CHANGED
|
@@ -136,5 +136,26 @@ export const aiSchema: PikkuSchema = {
|
|
|
136
136
|
.createIndex('idx_ai_run_thread')
|
|
137
137
|
.on('aiRun')
|
|
138
138
|
.columns(['threadId', 'createdAt']),
|
|
139
|
+
|
|
140
|
+
(db) =>
|
|
141
|
+
db.schema
|
|
142
|
+
.createTable('aiRunScore')
|
|
143
|
+
.addColumn('id', 'varchar(36)', (col) => col.primaryKey())
|
|
144
|
+
.addColumn('runId', 'varchar(36)', (col) =>
|
|
145
|
+
col.notNull().references('aiRun.runId').onDelete('cascade')
|
|
146
|
+
)
|
|
147
|
+
.addColumn('scorerName', 'varchar(255)', (col) => col.notNull())
|
|
148
|
+
.addColumn('score', 'real', (col) => col.notNull())
|
|
149
|
+
.addColumn('reason', 'text')
|
|
150
|
+
.addColumn('metadata', 'text')
|
|
151
|
+
.addColumn('createdAt', 'timestamp', (col) =>
|
|
152
|
+
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
153
|
+
),
|
|
154
|
+
|
|
155
|
+
(db) =>
|
|
156
|
+
db.schema
|
|
157
|
+
.createIndex('idx_ai_run_score_run')
|
|
158
|
+
.on('aiRunScore')
|
|
159
|
+
.columns(['runId', 'createdAt']),
|
|
139
160
|
],
|
|
140
161
|
}
|