@pikku/kysely 0.12.7 → 0.12.9
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 +38 -0
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.js +2 -1
- package/dist/src/kysely-ai-run-state-service.d.ts +20 -0
- package/dist/src/kysely-ai-run-state-service.js +172 -0
- package/dist/src/kysely-credential-service.d.ts +2 -0
- package/dist/src/kysely-credential-service.js +18 -0
- package/dist/src/kysely-deployment-service.d.ts +6 -3
- package/dist/src/kysely-deployment-service.js +27 -7
- package/dist/src/kysely-tables.d.ts +1 -1
- package/dist/src/kysely-workflow-run-service.js +1 -1
- package/dist/src/kysely-workflow-service.js +16 -3
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/index.ts +2 -1
- package/src/kysely-ai-run-state-service.ts +197 -0
- package/src/kysely-credential-service.ts +22 -0
- package/src/kysely-deployment-service.ts +42 -8
- package/src/kysely-services.test.ts +1 -1
- package/src/kysely-tables.ts +1 -1
- package/src/kysely-workflow-run-service.ts +1 -1
- package/src/kysely-workflow-service.ts +17 -6
- /package/dist/src/{kysely-agent-run-service.d.ts → kysely-ai-agent-run-service.d.ts} +0 -0
- /package/dist/src/{kysely-agent-run-service.js → kysely-ai-agent-run-service.js} +0 -0
- /package/src/{kysely-agent-run-service.ts → kysely-ai-agent-run-service.ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
## 0.12.0
|
|
2
2
|
|
|
3
|
+
## 0.12.9
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 624097e: Add deploy pipeline with provider-agnostic architecture
|
|
8
|
+
|
|
9
|
+
- Add MetaService with explicit typed API, absorb WiringService reads
|
|
10
|
+
- Add deployment service, traceId propagation, scoped logger
|
|
11
|
+
- Rewrite analyzer: one function = one worker, gateways dispatch via RPC
|
|
12
|
+
- Add Cloudflare deploy provider with plan/apply commands
|
|
13
|
+
- Add per-unit filtered codegen for deploy pipeline
|
|
14
|
+
- Skip missing metadata in wiring registration for deploy units
|
|
15
|
+
- Fix schema coercion crash when schema has no properties
|
|
16
|
+
- Fix E2E codegen: double-pass resolves cross-package Zod type imports
|
|
17
|
+
|
|
18
|
+
- Updated dependencies [9e8605f]
|
|
19
|
+
- Updated dependencies [624097e]
|
|
20
|
+
- Updated dependencies [7ab3243]
|
|
21
|
+
- @pikku/core@0.12.15
|
|
22
|
+
|
|
23
|
+
## 0.12.8
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- f85c234: Add unified credential system with per-user OAuth and AI agent pre-flight checks
|
|
28
|
+
|
|
29
|
+
- Unified CredentialService with lazy loading per user via pikkuUserId
|
|
30
|
+
- wire.getCredential() for typed single credential lookup
|
|
31
|
+
- MissingCredentialError with structured payload for client-side connect flows
|
|
32
|
+
- Console UI: Global/Users credential tabs, per-user OAuth connect/revoke
|
|
33
|
+
- AI agent pre-flight check: detects missing OAuth credentials from addon metadata, shows "Connect your accounts" prompt before chat
|
|
34
|
+
- CLI codegen: generates credentialsMeta per addon package for runtime lookup
|
|
35
|
+
- Vercel AI runner: catches MissingCredentialError as runtime fallback
|
|
36
|
+
|
|
37
|
+
- Updated dependencies [f85c234]
|
|
38
|
+
- Updated dependencies [88d3100]
|
|
39
|
+
- @pikku/core@0.12.14
|
|
40
|
+
|
|
3
41
|
## 0.12.7
|
|
4
42
|
|
|
5
43
|
### Patch Changes
|
package/dist/src/index.d.ts
CHANGED
|
@@ -4,7 +4,8 @@ export { KyselyWorkflowService } from './kysely-workflow-service.js';
|
|
|
4
4
|
export { KyselyWorkflowRunService } from './kysely-workflow-run-service.js';
|
|
5
5
|
export { KyselyDeploymentService } from './kysely-deployment-service.js';
|
|
6
6
|
export { KyselyAIStorageService } from './kysely-ai-storage-service.js';
|
|
7
|
-
export { KyselyAgentRunService } from './kysely-agent-run-service.js';
|
|
7
|
+
export { KyselyAgentRunService } from './kysely-ai-agent-run-service.js';
|
|
8
|
+
export { KyselyAIRunStateService } from './kysely-ai-run-state-service.js';
|
|
8
9
|
export { KyselySecretService } from './kysely-secret-service.js';
|
|
9
10
|
export type { KyselySecretServiceConfig } from './kysely-secret-service.js';
|
|
10
11
|
export { KyselyCredentialService } from './kysely-credential-service.js';
|
package/dist/src/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export { KyselyWorkflowService } from './kysely-workflow-service.js';
|
|
|
4
4
|
export { KyselyWorkflowRunService } from './kysely-workflow-run-service.js';
|
|
5
5
|
export { KyselyDeploymentService } from './kysely-deployment-service.js';
|
|
6
6
|
export { KyselyAIStorageService } from './kysely-ai-storage-service.js';
|
|
7
|
-
export { KyselyAgentRunService } from './kysely-agent-run-service.js';
|
|
7
|
+
export { KyselyAgentRunService } from './kysely-ai-agent-run-service.js';
|
|
8
|
+
export { KyselyAIRunStateService } from './kysely-ai-run-state-service.js';
|
|
8
9
|
export { KyselySecretService } from './kysely-secret-service.js';
|
|
9
10
|
export { KyselyCredentialService } from './kysely-credential-service.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Kysely } from 'kysely';
|
|
2
|
+
import type { KyselyPikkuDB } from './kysely-tables.js';
|
|
3
|
+
import type { AIRunStateService, CreateRunInput } from '@pikku/core/services';
|
|
4
|
+
import type { AgentRunState, PendingApproval } from '@pikku/core/ai-agent';
|
|
5
|
+
export declare class KyselyAIRunStateService implements AIRunStateService {
|
|
6
|
+
private db;
|
|
7
|
+
private initialized;
|
|
8
|
+
constructor(db: Kysely<KyselyPikkuDB>);
|
|
9
|
+
init(): Promise<void>;
|
|
10
|
+
createRun(run: CreateRunInput): Promise<string>;
|
|
11
|
+
updateRun(runId: string, updates: Partial<AgentRunState>): Promise<void>;
|
|
12
|
+
getRun(runId: string): Promise<AgentRunState | null>;
|
|
13
|
+
getRunsByThread(threadId: string): Promise<AgentRunState[]>;
|
|
14
|
+
resolveApproval(toolCallId: string, status: 'approved' | 'denied'): Promise<void>;
|
|
15
|
+
findRunByToolCallId(toolCallId: string): Promise<{
|
|
16
|
+
run: AgentRunState;
|
|
17
|
+
approval: PendingApproval;
|
|
18
|
+
} | null>;
|
|
19
|
+
private toRunState;
|
|
20
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { sql } from 'kysely';
|
|
2
|
+
export class KyselyAIRunStateService {
|
|
3
|
+
db;
|
|
4
|
+
initialized = false;
|
|
5
|
+
constructor(db) {
|
|
6
|
+
this.db = db;
|
|
7
|
+
}
|
|
8
|
+
async init() {
|
|
9
|
+
if (this.initialized)
|
|
10
|
+
return;
|
|
11
|
+
await this.db.schema
|
|
12
|
+
.createTable('aiRun')
|
|
13
|
+
.ifNotExists()
|
|
14
|
+
.addColumn('runId', 'text', (col) => col.primaryKey())
|
|
15
|
+
.addColumn('agentName', 'text', (col) => col.notNull())
|
|
16
|
+
.addColumn('threadId', 'text', (col) => col.notNull())
|
|
17
|
+
.addColumn('resourceId', 'text', (col) => col.notNull())
|
|
18
|
+
.addColumn('status', 'text', (col) => col.defaultTo('running').notNull())
|
|
19
|
+
.addColumn('errorMessage', 'text')
|
|
20
|
+
.addColumn('suspendReason', 'text')
|
|
21
|
+
.addColumn('missingRpcs', 'text')
|
|
22
|
+
.addColumn('pendingApprovals', 'text')
|
|
23
|
+
.addColumn('usageInputTokens', 'integer', (col) => col.defaultTo(0))
|
|
24
|
+
.addColumn('usageOutputTokens', 'integer', (col) => col.defaultTo(0))
|
|
25
|
+
.addColumn('usageModel', 'text', (col) => col.defaultTo(''))
|
|
26
|
+
.addColumn('createdAt', 'timestamp', (col) => col.defaultTo(sql `CURRENT_TIMESTAMP`).notNull())
|
|
27
|
+
.addColumn('updatedAt', 'timestamp', (col) => col.defaultTo(sql `CURRENT_TIMESTAMP`).notNull())
|
|
28
|
+
.execute();
|
|
29
|
+
this.initialized = true;
|
|
30
|
+
}
|
|
31
|
+
async createRun(run) {
|
|
32
|
+
const runId = `run-${crypto.randomUUID()}`;
|
|
33
|
+
await this.db
|
|
34
|
+
.insertInto('aiRun')
|
|
35
|
+
.values({
|
|
36
|
+
runId,
|
|
37
|
+
agentName: run.agentName,
|
|
38
|
+
threadId: run.threadId,
|
|
39
|
+
resourceId: run.resourceId,
|
|
40
|
+
status: run.status ?? 'running',
|
|
41
|
+
errorMessage: run.errorMessage ?? null,
|
|
42
|
+
suspendReason: run.suspendReason ?? null,
|
|
43
|
+
missingRpcs: run.missingRpcs ? JSON.stringify(run.missingRpcs) : null,
|
|
44
|
+
usageInputTokens: run.usage?.inputTokens ?? 0,
|
|
45
|
+
usageOutputTokens: run.usage?.outputTokens ?? 0,
|
|
46
|
+
usageModel: run.usage?.model ?? '',
|
|
47
|
+
})
|
|
48
|
+
.execute();
|
|
49
|
+
return runId;
|
|
50
|
+
}
|
|
51
|
+
async updateRun(runId, updates) {
|
|
52
|
+
const values = {
|
|
53
|
+
updatedAt: sql `CURRENT_TIMESTAMP`,
|
|
54
|
+
};
|
|
55
|
+
if (updates.status !== undefined)
|
|
56
|
+
values.status = updates.status;
|
|
57
|
+
if (updates.errorMessage !== undefined)
|
|
58
|
+
values.errorMessage = updates.errorMessage;
|
|
59
|
+
if (updates.suspendReason !== undefined)
|
|
60
|
+
values.suspendReason = updates.suspendReason;
|
|
61
|
+
if (updates.missingRpcs !== undefined)
|
|
62
|
+
values.missingRpcs = JSON.stringify(updates.missingRpcs);
|
|
63
|
+
if (updates.pendingApprovals !== undefined)
|
|
64
|
+
values.pendingApprovals = JSON.stringify(updates.pendingApprovals);
|
|
65
|
+
if (updates.usage) {
|
|
66
|
+
values.usageInputTokens = updates.usage.inputTokens;
|
|
67
|
+
values.usageOutputTokens = updates.usage.outputTokens;
|
|
68
|
+
values.usageModel = updates.usage.model;
|
|
69
|
+
}
|
|
70
|
+
await this.db
|
|
71
|
+
.updateTable('aiRun')
|
|
72
|
+
.set(values)
|
|
73
|
+
.where('runId', '=', runId)
|
|
74
|
+
.execute();
|
|
75
|
+
}
|
|
76
|
+
async getRun(runId) {
|
|
77
|
+
const row = await this.db
|
|
78
|
+
.selectFrom('aiRun')
|
|
79
|
+
.selectAll()
|
|
80
|
+
.where('runId', '=', runId)
|
|
81
|
+
.executeTakeFirst();
|
|
82
|
+
return row ? this.toRunState(row) : null;
|
|
83
|
+
}
|
|
84
|
+
async getRunsByThread(threadId) {
|
|
85
|
+
const rows = await this.db
|
|
86
|
+
.selectFrom('aiRun')
|
|
87
|
+
.selectAll()
|
|
88
|
+
.where('threadId', '=', threadId)
|
|
89
|
+
.orderBy('createdAt', 'desc')
|
|
90
|
+
.execute();
|
|
91
|
+
return rows.map((r) => this.toRunState(r));
|
|
92
|
+
}
|
|
93
|
+
async resolveApproval(toolCallId, status) {
|
|
94
|
+
const rows = await this.db
|
|
95
|
+
.selectFrom('aiRun')
|
|
96
|
+
.select(['runId', 'pendingApprovals'])
|
|
97
|
+
.where('status', '=', 'suspended')
|
|
98
|
+
.execute();
|
|
99
|
+
for (const row of rows) {
|
|
100
|
+
let approvals = [];
|
|
101
|
+
if (row.pendingApprovals) {
|
|
102
|
+
try {
|
|
103
|
+
approvals = JSON.parse(row.pendingApprovals);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
console.warn(`Failed to parse pendingApprovals for run ${row.runId}, treating as empty`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const filtered = approvals.filter((a) => a.toolCallId !== toolCallId);
|
|
110
|
+
if (filtered.length !== approvals.length) {
|
|
111
|
+
const updates = {
|
|
112
|
+
pendingApprovals: filtered.length > 0 ? JSON.stringify(filtered) : null,
|
|
113
|
+
updatedAt: sql `CURRENT_TIMESTAMP`,
|
|
114
|
+
};
|
|
115
|
+
if (filtered.length === 0) {
|
|
116
|
+
updates.status = status;
|
|
117
|
+
}
|
|
118
|
+
await this.db
|
|
119
|
+
.updateTable('aiRun')
|
|
120
|
+
.set(updates)
|
|
121
|
+
.where('runId', '=', row.runId)
|
|
122
|
+
.execute();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async findRunByToolCallId(toolCallId) {
|
|
128
|
+
const rows = await this.db
|
|
129
|
+
.selectFrom('aiRun')
|
|
130
|
+
.selectAll()
|
|
131
|
+
.where('status', '=', 'suspended')
|
|
132
|
+
.execute();
|
|
133
|
+
for (const row of rows) {
|
|
134
|
+
let approvals = [];
|
|
135
|
+
if (row.pendingApprovals) {
|
|
136
|
+
try {
|
|
137
|
+
approvals = JSON.parse(row.pendingApprovals);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
console.warn(`Failed to parse pendingApprovals for run ${row.runId}, treating as empty`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const approval = approvals.find((a) => a.toolCallId === toolCallId);
|
|
144
|
+
if (approval) {
|
|
145
|
+
return { run: this.toRunState(row), approval };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
toRunState(row) {
|
|
151
|
+
return {
|
|
152
|
+
runId: row.runId,
|
|
153
|
+
agentName: row.agentName,
|
|
154
|
+
threadId: row.threadId,
|
|
155
|
+
resourceId: row.resourceId,
|
|
156
|
+
status: row.status,
|
|
157
|
+
errorMessage: row.errorMessage ?? undefined,
|
|
158
|
+
suspendReason: row.suspendReason ?? undefined,
|
|
159
|
+
missingRpcs: row.missingRpcs ? JSON.parse(row.missingRpcs) : undefined,
|
|
160
|
+
pendingApprovals: row.pendingApprovals
|
|
161
|
+
? JSON.parse(row.pendingApprovals)
|
|
162
|
+
: undefined,
|
|
163
|
+
usage: {
|
|
164
|
+
inputTokens: row.usageInputTokens ?? 0,
|
|
165
|
+
outputTokens: row.usageOutputTokens ?? 0,
|
|
166
|
+
model: row.usageModel ?? '',
|
|
167
|
+
},
|
|
168
|
+
createdAt: new Date(row.createdAt),
|
|
169
|
+
updatedAt: new Date(row.updatedAt),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -26,5 +26,7 @@ export declare class KyselyCredentialService implements CredentialService {
|
|
|
26
26
|
delete(name: string, userId?: string): Promise<void>;
|
|
27
27
|
has(name: string, userId?: string): Promise<boolean>;
|
|
28
28
|
getAll(userId: string): Promise<Record<string, unknown>>;
|
|
29
|
+
getUsersWithCredential(name: string): Promise<string[]>;
|
|
30
|
+
getAllUsers(): Promise<string[]>;
|
|
29
31
|
rotateKEK(): Promise<number>;
|
|
30
32
|
}
|
|
@@ -160,6 +160,24 @@ export class KyselyCredentialService {
|
|
|
160
160
|
}
|
|
161
161
|
return result;
|
|
162
162
|
}
|
|
163
|
+
async getUsersWithCredential(name) {
|
|
164
|
+
const rows = await this.db
|
|
165
|
+
.selectFrom('credentials')
|
|
166
|
+
.select('userId')
|
|
167
|
+
.where('name', '=', name)
|
|
168
|
+
.where('userId', 'is not', null)
|
|
169
|
+
.execute();
|
|
170
|
+
return rows.map((row) => row.userId).filter(Boolean);
|
|
171
|
+
}
|
|
172
|
+
async getAllUsers() {
|
|
173
|
+
const rows = await this.db
|
|
174
|
+
.selectFrom('credentials')
|
|
175
|
+
.select('userId')
|
|
176
|
+
.distinct()
|
|
177
|
+
.where('userId', 'is not', null)
|
|
178
|
+
.execute();
|
|
179
|
+
return rows.map((row) => row.userId).filter(Boolean);
|
|
180
|
+
}
|
|
163
181
|
async rotateKEK() {
|
|
164
182
|
if (!this.previousKey) {
|
|
165
183
|
throw new Error('No previousKey configured — nothing to rotate from');
|
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
import type { DeploymentService, DeploymentServiceConfig, DeploymentConfig
|
|
1
|
+
import type { DeploymentService, DeploymentServiceConfig, DeploymentConfig } from '@pikku/core/services';
|
|
2
|
+
import type { JWTService, SecretService } from '@pikku/core/services';
|
|
2
3
|
import type { Kysely } from 'kysely';
|
|
3
4
|
import type { KyselyPikkuDB } from './kysely-tables.js';
|
|
4
5
|
export declare class KyselyDeploymentService implements DeploymentService {
|
|
5
6
|
protected db: Kysely<KyselyPikkuDB>;
|
|
7
|
+
private jwt?;
|
|
8
|
+
private secrets?;
|
|
6
9
|
private initialized;
|
|
7
10
|
private heartbeatTimer?;
|
|
8
11
|
private deploymentConfig?;
|
|
9
12
|
private heartbeatInterval;
|
|
10
13
|
private heartbeatTtl;
|
|
11
|
-
constructor(config: DeploymentServiceConfig, db: Kysely<KyselyPikkuDB
|
|
14
|
+
constructor(config: DeploymentServiceConfig, db: Kysely<KyselyPikkuDB>, jwt?: JWTService | undefined, secrets?: SecretService | undefined);
|
|
12
15
|
init(): Promise<void>;
|
|
13
16
|
start(config: DeploymentConfig): Promise<void>;
|
|
14
17
|
stop(): Promise<void>;
|
|
15
|
-
|
|
18
|
+
invoke(funcName: string, data: unknown, session?: unknown, traceId?: string): Promise<unknown>;
|
|
16
19
|
private createIndexSafe;
|
|
17
20
|
private sendHeartbeat;
|
|
18
21
|
}
|
|
@@ -1,14 +1,19 @@
|
|
|
1
|
+
import { buildRemoteHeaders } from '@pikku/core/remote';
|
|
1
2
|
import { getAllFunctionNames } from '@pikku/core/function';
|
|
2
3
|
import { sql } from 'kysely';
|
|
3
4
|
export class KyselyDeploymentService {
|
|
4
5
|
db;
|
|
6
|
+
jwt;
|
|
7
|
+
secrets;
|
|
5
8
|
initialized = false;
|
|
6
9
|
heartbeatTimer;
|
|
7
10
|
deploymentConfig;
|
|
8
11
|
heartbeatInterval;
|
|
9
12
|
heartbeatTtl;
|
|
10
|
-
constructor(config, db) {
|
|
13
|
+
constructor(config, db, jwt, secrets) {
|
|
11
14
|
this.db = db;
|
|
15
|
+
this.jwt = jwt;
|
|
16
|
+
this.secrets = secrets;
|
|
12
17
|
this.heartbeatInterval = config.heartbeatInterval ?? 10000;
|
|
13
18
|
this.heartbeatTtl = config.heartbeatTtl ?? 30000;
|
|
14
19
|
}
|
|
@@ -93,21 +98,36 @@ export class KyselyDeploymentService {
|
|
|
93
98
|
.execute();
|
|
94
99
|
}
|
|
95
100
|
}
|
|
96
|
-
async
|
|
101
|
+
async invoke(funcName, data, session, traceId) {
|
|
102
|
+
const headers = await buildRemoteHeaders(this.jwt, this.secrets, funcName, session, traceId);
|
|
97
103
|
const ttlMs = this.heartbeatTtl;
|
|
98
104
|
const cutoff = new Date(Date.now() - ttlMs);
|
|
99
105
|
const result = await this.db
|
|
100
106
|
.selectFrom('pikkuDeployments as d')
|
|
101
107
|
.innerJoin('pikkuDeploymentFunctions as f', 'f.deploymentId', 'd.deploymentId')
|
|
102
108
|
.select(['d.deploymentId', 'd.endpoint'])
|
|
103
|
-
.where('f.functionName', '=',
|
|
109
|
+
.where('f.functionName', '=', funcName)
|
|
104
110
|
.where('d.lastHeartbeat', '>', cutoff)
|
|
105
111
|
.orderBy('d.lastHeartbeat', 'desc')
|
|
112
|
+
.limit(1)
|
|
106
113
|
.execute();
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
114
|
+
if (result.length === 0) {
|
|
115
|
+
throw new Error(`No deployment found for function '${funcName}'`);
|
|
116
|
+
}
|
|
117
|
+
const endpoint = result[0].endpoint;
|
|
118
|
+
const url = `${endpoint}/remote/rpc/${encodeURIComponent(funcName)}`;
|
|
119
|
+
const response = await fetch(url, {
|
|
120
|
+
method: 'POST',
|
|
121
|
+
headers: {
|
|
122
|
+
'content-type': 'application/json',
|
|
123
|
+
...headers,
|
|
124
|
+
},
|
|
125
|
+
body: JSON.stringify({ data }),
|
|
126
|
+
});
|
|
127
|
+
if (!response.ok) {
|
|
128
|
+
throw new Error(`Remote RPC call to '${funcName}' failed: ${response.status}`);
|
|
129
|
+
}
|
|
130
|
+
return response.json();
|
|
111
131
|
}
|
|
112
132
|
async createIndexSafe(builder) {
|
|
113
133
|
try {
|
|
@@ -105,7 +105,7 @@ export interface AIRunTable {
|
|
|
105
105
|
resourceId: string;
|
|
106
106
|
status: Generated<'running' | 'suspended' | 'completed' | 'failed'>;
|
|
107
107
|
errorMessage: string | null;
|
|
108
|
-
suspendReason: 'approval' | 'rpc-missing' | null;
|
|
108
|
+
suspendReason: 'approval' | 'credential' | 'rpc-missing' | null;
|
|
109
109
|
missingRpcs: string | null;
|
|
110
110
|
usageInputTokens: Generated<number>;
|
|
111
111
|
usageOutputTokens: Generated<number>;
|
|
@@ -175,7 +175,7 @@ export class KyselyWorkflowRunService {
|
|
|
175
175
|
let query = this.db
|
|
176
176
|
.selectFrom('workflowVersions')
|
|
177
177
|
.select(['workflowName', 'graphHash', 'graph'])
|
|
178
|
-
.where('source', '=', '
|
|
178
|
+
.where('source', '=', 'dynamic-workflow')
|
|
179
179
|
.where('status', '=', 'active');
|
|
180
180
|
if (agentName) {
|
|
181
181
|
query = query.where('workflowName', 'like', `ai:${agentName}:%`);
|
|
@@ -105,8 +105,8 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
|
|
|
105
105
|
status: 'running',
|
|
106
106
|
input: JSON.stringify(input),
|
|
107
107
|
inline,
|
|
108
|
-
graphHash: graphHash,
|
|
109
|
-
wire: JSON.stringify(wire),
|
|
108
|
+
graphHash: graphHash ?? null,
|
|
109
|
+
wire: wire ? JSON.stringify(wire) : null,
|
|
110
110
|
})
|
|
111
111
|
.execute();
|
|
112
112
|
return id;
|
|
@@ -486,7 +486,20 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
|
|
|
486
486
|
return this.runService.getWorkflowVersion(name, graphHash);
|
|
487
487
|
}
|
|
488
488
|
async getAIGeneratedWorkflows(agentName) {
|
|
489
|
-
|
|
489
|
+
let query = this.db
|
|
490
|
+
.selectFrom('workflowVersions')
|
|
491
|
+
.select(['workflowName', 'graphHash', 'graph'])
|
|
492
|
+
.where('source', '=', 'dynamic-workflow')
|
|
493
|
+
.where('status', '=', 'active');
|
|
494
|
+
if (agentName) {
|
|
495
|
+
query = query.where('workflowName', 'like', `ai:${agentName}:%`);
|
|
496
|
+
}
|
|
497
|
+
const rows = await query.execute();
|
|
498
|
+
return rows.map((row) => ({
|
|
499
|
+
workflowName: row.workflowName,
|
|
500
|
+
graphHash: row.graphHash,
|
|
501
|
+
graph: typeof row.graph === 'string' ? JSON.parse(row.graph) : row.graph,
|
|
502
|
+
}));
|
|
490
503
|
}
|
|
491
504
|
async close() { }
|
|
492
505
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/index.ts","../src/kysely-agent-run-service.ts","../src/kysely-ai-storage-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-secret-service.ts","../src/kysely-tables.ts","../src/kysely-workflow-run-service.ts","../src/kysely-workflow-service.ts","../bin/pikku-kysely-pure.ts"],"version":"5.9.3"}
|
|
1
|
+
{"root":["../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-channel-store.ts","../src/kysely-credential-service.ts","../src/kysely-deployment-service.ts","../src/kysely-eventhub-store.ts","../src/kysely-json.ts","../src/kysely-secret-service.ts","../src/kysely-tables.ts","../src/kysely-workflow-run-service.ts","../src/kysely-workflow-service.ts","../bin/pikku-kysely-pure.ts"],"version":"5.9.3"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/kysely",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.9",
|
|
4
4
|
"author": "yasser.fadl@gmail.com",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"module": "dist/src/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"prepublishOnly": "yarn build"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
|
23
|
-
"@pikku/core": "^0.12.
|
|
23
|
+
"@pikku/core": "^0.12.15"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"kysely": "^0.28.12"
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,8 @@ export { KyselyWorkflowService } from './kysely-workflow-service.js'
|
|
|
4
4
|
export { KyselyWorkflowRunService } from './kysely-workflow-run-service.js'
|
|
5
5
|
export { KyselyDeploymentService } from './kysely-deployment-service.js'
|
|
6
6
|
export { KyselyAIStorageService } from './kysely-ai-storage-service.js'
|
|
7
|
-
export { KyselyAgentRunService } from './kysely-agent-run-service.js'
|
|
7
|
+
export { KyselyAgentRunService } from './kysely-ai-agent-run-service.js'
|
|
8
|
+
export { KyselyAIRunStateService } from './kysely-ai-run-state-service.js'
|
|
8
9
|
export { KyselySecretService } from './kysely-secret-service.js'
|
|
9
10
|
export type { KyselySecretServiceConfig } from './kysely-secret-service.js'
|
|
10
11
|
export { KyselyCredentialService } from './kysely-credential-service.js'
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { sql } from 'kysely'
|
|
2
|
+
import type { Kysely } from 'kysely'
|
|
3
|
+
import type { KyselyPikkuDB } from './kysely-tables.js'
|
|
4
|
+
import type { AIRunStateService, CreateRunInput } from '@pikku/core/services'
|
|
5
|
+
import type { AgentRunState, PendingApproval } from '@pikku/core/ai-agent'
|
|
6
|
+
|
|
7
|
+
export class KyselyAIRunStateService implements AIRunStateService {
|
|
8
|
+
private initialized = false
|
|
9
|
+
|
|
10
|
+
constructor(private db: Kysely<KyselyPikkuDB>) {}
|
|
11
|
+
|
|
12
|
+
async init(): Promise<void> {
|
|
13
|
+
if (this.initialized) return
|
|
14
|
+
|
|
15
|
+
await this.db.schema
|
|
16
|
+
.createTable('aiRun')
|
|
17
|
+
.ifNotExists()
|
|
18
|
+
.addColumn('runId', 'text', (col) => col.primaryKey())
|
|
19
|
+
.addColumn('agentName', 'text', (col) => col.notNull())
|
|
20
|
+
.addColumn('threadId', 'text', (col) => col.notNull())
|
|
21
|
+
.addColumn('resourceId', 'text', (col) => col.notNull())
|
|
22
|
+
.addColumn('status', 'text', (col) => col.defaultTo('running').notNull())
|
|
23
|
+
.addColumn('errorMessage', 'text')
|
|
24
|
+
.addColumn('suspendReason', 'text')
|
|
25
|
+
.addColumn('missingRpcs', 'text')
|
|
26
|
+
.addColumn('pendingApprovals', 'text')
|
|
27
|
+
.addColumn('usageInputTokens', 'integer', (col) => col.defaultTo(0))
|
|
28
|
+
.addColumn('usageOutputTokens', 'integer', (col) => col.defaultTo(0))
|
|
29
|
+
.addColumn('usageModel', 'text', (col) => col.defaultTo(''))
|
|
30
|
+
.addColumn('createdAt', 'timestamp', (col) =>
|
|
31
|
+
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
32
|
+
)
|
|
33
|
+
.addColumn('updatedAt', 'timestamp', (col) =>
|
|
34
|
+
col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()
|
|
35
|
+
)
|
|
36
|
+
.execute()
|
|
37
|
+
|
|
38
|
+
this.initialized = true
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async createRun(run: CreateRunInput): Promise<string> {
|
|
42
|
+
const runId = `run-${crypto.randomUUID()}`
|
|
43
|
+
await this.db
|
|
44
|
+
.insertInto('aiRun')
|
|
45
|
+
.values({
|
|
46
|
+
runId,
|
|
47
|
+
agentName: run.agentName,
|
|
48
|
+
threadId: run.threadId,
|
|
49
|
+
resourceId: run.resourceId,
|
|
50
|
+
status: run.status ?? 'running',
|
|
51
|
+
errorMessage: run.errorMessage ?? null,
|
|
52
|
+
suspendReason: run.suspendReason ?? null,
|
|
53
|
+
missingRpcs: run.missingRpcs ? JSON.stringify(run.missingRpcs) : null,
|
|
54
|
+
usageInputTokens: run.usage?.inputTokens ?? 0,
|
|
55
|
+
usageOutputTokens: run.usage?.outputTokens ?? 0,
|
|
56
|
+
usageModel: run.usage?.model ?? '',
|
|
57
|
+
})
|
|
58
|
+
.execute()
|
|
59
|
+
return runId
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async updateRun(
|
|
63
|
+
runId: string,
|
|
64
|
+
updates: Partial<AgentRunState>
|
|
65
|
+
): Promise<void> {
|
|
66
|
+
const values: Record<string, unknown> = {
|
|
67
|
+
updatedAt: sql`CURRENT_TIMESTAMP`,
|
|
68
|
+
}
|
|
69
|
+
if (updates.status !== undefined) values.status = updates.status
|
|
70
|
+
if (updates.errorMessage !== undefined)
|
|
71
|
+
values.errorMessage = updates.errorMessage
|
|
72
|
+
if (updates.suspendReason !== undefined)
|
|
73
|
+
values.suspendReason = updates.suspendReason
|
|
74
|
+
if (updates.missingRpcs !== undefined)
|
|
75
|
+
values.missingRpcs = JSON.stringify(updates.missingRpcs)
|
|
76
|
+
if (updates.pendingApprovals !== undefined)
|
|
77
|
+
values.pendingApprovals = JSON.stringify(updates.pendingApprovals)
|
|
78
|
+
if (updates.usage) {
|
|
79
|
+
values.usageInputTokens = updates.usage.inputTokens
|
|
80
|
+
values.usageOutputTokens = updates.usage.outputTokens
|
|
81
|
+
values.usageModel = updates.usage.model
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
await this.db
|
|
85
|
+
.updateTable('aiRun')
|
|
86
|
+
.set(values)
|
|
87
|
+
.where('runId', '=', runId)
|
|
88
|
+
.execute()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async getRun(runId: string): Promise<AgentRunState | null> {
|
|
92
|
+
const row = await this.db
|
|
93
|
+
.selectFrom('aiRun')
|
|
94
|
+
.selectAll()
|
|
95
|
+
.where('runId', '=', runId)
|
|
96
|
+
.executeTakeFirst()
|
|
97
|
+
return row ? this.toRunState(row) : null
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async getRunsByThread(threadId: string): Promise<AgentRunState[]> {
|
|
101
|
+
const rows = await this.db
|
|
102
|
+
.selectFrom('aiRun')
|
|
103
|
+
.selectAll()
|
|
104
|
+
.where('threadId', '=', threadId)
|
|
105
|
+
.orderBy('createdAt', 'desc')
|
|
106
|
+
.execute()
|
|
107
|
+
return rows.map((r) => this.toRunState(r))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async resolveApproval(
|
|
111
|
+
toolCallId: string,
|
|
112
|
+
status: 'approved' | 'denied'
|
|
113
|
+
): Promise<void> {
|
|
114
|
+
const rows = await this.db
|
|
115
|
+
.selectFrom('aiRun')
|
|
116
|
+
.select(['runId', 'pendingApprovals' as any])
|
|
117
|
+
.where('status', '=', 'suspended')
|
|
118
|
+
.execute()
|
|
119
|
+
|
|
120
|
+
for (const row of rows) {
|
|
121
|
+
let approvals: PendingApproval[] = []
|
|
122
|
+
if (row.pendingApprovals) {
|
|
123
|
+
try {
|
|
124
|
+
approvals = JSON.parse(row.pendingApprovals as string)
|
|
125
|
+
} catch {
|
|
126
|
+
console.warn(`Failed to parse pendingApprovals for run ${row.runId}, treating as empty`)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const filtered = approvals.filter((a) => a.toolCallId !== toolCallId)
|
|
130
|
+
if (filtered.length !== approvals.length) {
|
|
131
|
+
const updates: Record<string, unknown> = {
|
|
132
|
+
pendingApprovals:
|
|
133
|
+
filtered.length > 0 ? JSON.stringify(filtered) : null,
|
|
134
|
+
updatedAt: sql`CURRENT_TIMESTAMP`,
|
|
135
|
+
}
|
|
136
|
+
if (filtered.length === 0) {
|
|
137
|
+
updates.status = status
|
|
138
|
+
}
|
|
139
|
+
await this.db
|
|
140
|
+
.updateTable('aiRun')
|
|
141
|
+
.set(updates as any)
|
|
142
|
+
.where('runId', '=', row.runId)
|
|
143
|
+
.execute()
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async findRunByToolCallId(
|
|
150
|
+
toolCallId: string
|
|
151
|
+
): Promise<{ run: AgentRunState; approval: PendingApproval } | null> {
|
|
152
|
+
const rows = await this.db
|
|
153
|
+
.selectFrom('aiRun')
|
|
154
|
+
.selectAll()
|
|
155
|
+
.where('status', '=', 'suspended')
|
|
156
|
+
.execute()
|
|
157
|
+
|
|
158
|
+
for (const row of rows) {
|
|
159
|
+
let approvals: PendingApproval[] = []
|
|
160
|
+
if ((row as any).pendingApprovals) {
|
|
161
|
+
try {
|
|
162
|
+
approvals = JSON.parse((row as any).pendingApprovals)
|
|
163
|
+
} catch {
|
|
164
|
+
console.warn(`Failed to parse pendingApprovals for run ${row.runId}, treating as empty`)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const approval = approvals.find((a) => a.toolCallId === toolCallId)
|
|
168
|
+
if (approval) {
|
|
169
|
+
return { run: this.toRunState(row), approval }
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return null
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private toRunState(row: any): AgentRunState {
|
|
176
|
+
return {
|
|
177
|
+
runId: row.runId,
|
|
178
|
+
agentName: row.agentName,
|
|
179
|
+
threadId: row.threadId,
|
|
180
|
+
resourceId: row.resourceId,
|
|
181
|
+
status: row.status,
|
|
182
|
+
errorMessage: row.errorMessage ?? undefined,
|
|
183
|
+
suspendReason: row.suspendReason ?? undefined,
|
|
184
|
+
missingRpcs: row.missingRpcs ? JSON.parse(row.missingRpcs) : undefined,
|
|
185
|
+
pendingApprovals: row.pendingApprovals
|
|
186
|
+
? JSON.parse(row.pendingApprovals)
|
|
187
|
+
: undefined,
|
|
188
|
+
usage: {
|
|
189
|
+
inputTokens: row.usageInputTokens ?? 0,
|
|
190
|
+
outputTokens: row.usageOutputTokens ?? 0,
|
|
191
|
+
model: row.usageModel ?? '',
|
|
192
|
+
},
|
|
193
|
+
createdAt: new Date(row.createdAt),
|
|
194
|
+
updatedAt: new Date(row.updatedAt),
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -216,6 +216,28 @@ export class KyselyCredentialService implements CredentialService {
|
|
|
216
216
|
return result
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
async getUsersWithCredential(name: string): Promise<string[]> {
|
|
220
|
+
const rows = await this.db
|
|
221
|
+
.selectFrom('credentials')
|
|
222
|
+
.select('userId')
|
|
223
|
+
.where('name', '=', name)
|
|
224
|
+
.where('userId', 'is not', null)
|
|
225
|
+
.execute()
|
|
226
|
+
|
|
227
|
+
return rows.map((row) => row.userId!).filter(Boolean)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async getAllUsers(): Promise<string[]> {
|
|
231
|
+
const rows = await this.db
|
|
232
|
+
.selectFrom('credentials')
|
|
233
|
+
.select('userId')
|
|
234
|
+
.distinct()
|
|
235
|
+
.where('userId', 'is not', null)
|
|
236
|
+
.execute()
|
|
237
|
+
|
|
238
|
+
return rows.map((row) => row.userId!).filter(Boolean)
|
|
239
|
+
}
|
|
240
|
+
|
|
219
241
|
async rotateKEK(): Promise<number> {
|
|
220
242
|
if (!this.previousKey) {
|
|
221
243
|
throw new Error('No previousKey configured — nothing to rotate from')
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { buildRemoteHeaders } from '@pikku/core/remote'
|
|
1
2
|
import type {
|
|
2
3
|
DeploymentService,
|
|
3
4
|
DeploymentServiceConfig,
|
|
4
5
|
DeploymentConfig,
|
|
5
|
-
DeploymentInfo,
|
|
6
6
|
} from '@pikku/core/services'
|
|
7
|
+
import type { JWTService, SecretService } from '@pikku/core/services'
|
|
7
8
|
import { getAllFunctionNames } from '@pikku/core/function'
|
|
8
9
|
import type { Kysely } from 'kysely'
|
|
9
10
|
import { sql } from 'kysely'
|
|
@@ -18,7 +19,9 @@ export class KyselyDeploymentService implements DeploymentService {
|
|
|
18
19
|
|
|
19
20
|
constructor(
|
|
20
21
|
config: DeploymentServiceConfig,
|
|
21
|
-
protected db: Kysely<KyselyPikkuDB
|
|
22
|
+
protected db: Kysely<KyselyPikkuDB>,
|
|
23
|
+
private jwt?: JWTService,
|
|
24
|
+
private secrets?: SecretService
|
|
22
25
|
) {
|
|
23
26
|
this.heartbeatInterval = config.heartbeatInterval ?? 10000
|
|
24
27
|
this.heartbeatTtl = config.heartbeatTtl ?? 30000
|
|
@@ -135,7 +138,19 @@ export class KyselyDeploymentService implements DeploymentService {
|
|
|
135
138
|
}
|
|
136
139
|
}
|
|
137
140
|
|
|
138
|
-
async
|
|
141
|
+
async invoke(
|
|
142
|
+
funcName: string,
|
|
143
|
+
data: unknown,
|
|
144
|
+
session?: unknown,
|
|
145
|
+
traceId?: string
|
|
146
|
+
): Promise<unknown> {
|
|
147
|
+
const headers = await buildRemoteHeaders(
|
|
148
|
+
this.jwt,
|
|
149
|
+
this.secrets,
|
|
150
|
+
funcName,
|
|
151
|
+
session,
|
|
152
|
+
traceId
|
|
153
|
+
)
|
|
139
154
|
const ttlMs = this.heartbeatTtl
|
|
140
155
|
const cutoff = new Date(Date.now() - ttlMs)
|
|
141
156
|
|
|
@@ -147,15 +162,34 @@ export class KyselyDeploymentService implements DeploymentService {
|
|
|
147
162
|
'd.deploymentId'
|
|
148
163
|
)
|
|
149
164
|
.select(['d.deploymentId', 'd.endpoint'])
|
|
150
|
-
.where('f.functionName', '=',
|
|
165
|
+
.where('f.functionName', '=', funcName)
|
|
151
166
|
.where('d.lastHeartbeat', '>', cutoff)
|
|
152
167
|
.orderBy('d.lastHeartbeat', 'desc')
|
|
168
|
+
.limit(1)
|
|
153
169
|
.execute()
|
|
154
170
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
171
|
+
if (result.length === 0) {
|
|
172
|
+
throw new Error(`No deployment found for function '${funcName}'`)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const endpoint = result[0].endpoint
|
|
176
|
+
const url = `${endpoint}/remote/rpc/${encodeURIComponent(funcName)}`
|
|
177
|
+
const response = await fetch(url, {
|
|
178
|
+
method: 'POST',
|
|
179
|
+
headers: {
|
|
180
|
+
'content-type': 'application/json',
|
|
181
|
+
...headers,
|
|
182
|
+
},
|
|
183
|
+
body: JSON.stringify({ data }),
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
if (!response.ok) {
|
|
187
|
+
throw new Error(
|
|
188
|
+
`Remote RPC call to '${funcName}' failed: ${response.status}`
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return response.json()
|
|
159
193
|
}
|
|
160
194
|
|
|
161
195
|
private async createIndexSafe(builder: {
|
|
@@ -14,7 +14,7 @@ import { KyselyWorkflowService } from './kysely-workflow-service.js'
|
|
|
14
14
|
import { KyselyWorkflowRunService } from './kysely-workflow-run-service.js'
|
|
15
15
|
import { KyselyDeploymentService } from './kysely-deployment-service.js'
|
|
16
16
|
import { KyselyAIStorageService } from './kysely-ai-storage-service.js'
|
|
17
|
-
import { KyselyAgentRunService } from './kysely-agent-run-service.js'
|
|
17
|
+
import { KyselyAgentRunService } from './kysely-ai-agent-run-service.js'
|
|
18
18
|
import { KyselySecretService } from './kysely-secret-service.js'
|
|
19
19
|
import { KyselyCredentialService } from './kysely-credential-service.js'
|
|
20
20
|
|
package/src/kysely-tables.ts
CHANGED
|
@@ -120,7 +120,7 @@ export interface AIRunTable {
|
|
|
120
120
|
resourceId: string
|
|
121
121
|
status: Generated<'running' | 'suspended' | 'completed' | 'failed'>
|
|
122
122
|
errorMessage: string | null
|
|
123
|
-
suspendReason: 'approval' | 'rpc-missing' | null
|
|
123
|
+
suspendReason: 'approval' | 'credential' | 'rpc-missing' | null
|
|
124
124
|
missingRpcs: string | null
|
|
125
125
|
usageInputTokens: Generated<number>
|
|
126
126
|
usageOutputTokens: Generated<number>
|
|
@@ -223,7 +223,7 @@ export class KyselyWorkflowRunService implements WorkflowRunService {
|
|
|
223
223
|
let query = this.db
|
|
224
224
|
.selectFrom('workflowVersions')
|
|
225
225
|
.select(['workflowName', 'graphHash', 'graph'])
|
|
226
|
-
.where('source', '=', '
|
|
226
|
+
.where('source', '=', 'dynamic-workflow')
|
|
227
227
|
.where('status', '=', 'active')
|
|
228
228
|
if (agentName) {
|
|
229
229
|
query = query.where('workflowName', 'like', `ai:${agentName}:%`)
|
|
@@ -150,8 +150,8 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
|
|
|
150
150
|
status: 'running',
|
|
151
151
|
input: JSON.stringify(input),
|
|
152
152
|
inline,
|
|
153
|
-
graphHash: graphHash,
|
|
154
|
-
wire: JSON.stringify(wire),
|
|
153
|
+
graphHash: graphHash ?? null,
|
|
154
|
+
wire: wire ? JSON.stringify(wire) : null,
|
|
155
155
|
})
|
|
156
156
|
.execute()
|
|
157
157
|
|
|
@@ -625,9 +625,7 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
|
|
|
625
625
|
source,
|
|
626
626
|
status: status ?? 'active',
|
|
627
627
|
})
|
|
628
|
-
.onConflict((oc) =>
|
|
629
|
-
oc.columns(['workflowName', 'graphHash']).doNothing()
|
|
630
|
-
)
|
|
628
|
+
.onConflict((oc) => oc.columns(['workflowName', 'graphHash']).doNothing())
|
|
631
629
|
.execute()
|
|
632
630
|
}
|
|
633
631
|
|
|
@@ -654,7 +652,20 @@ export class KyselyWorkflowService extends PikkuWorkflowService {
|
|
|
654
652
|
async getAIGeneratedWorkflows(
|
|
655
653
|
agentName?: string
|
|
656
654
|
): Promise<Array<{ workflowName: string; graphHash: string; graph: any }>> {
|
|
657
|
-
|
|
655
|
+
let query = this.db
|
|
656
|
+
.selectFrom('workflowVersions')
|
|
657
|
+
.select(['workflowName', 'graphHash', 'graph'])
|
|
658
|
+
.where('source', '=', 'dynamic-workflow')
|
|
659
|
+
.where('status', '=', 'active')
|
|
660
|
+
if (agentName) {
|
|
661
|
+
query = query.where('workflowName', 'like', `ai:${agentName}:%`)
|
|
662
|
+
}
|
|
663
|
+
const rows = await query.execute()
|
|
664
|
+
return rows.map((row) => ({
|
|
665
|
+
workflowName: row.workflowName,
|
|
666
|
+
graphHash: row.graphHash,
|
|
667
|
+
graph: typeof row.graph === 'string' ? JSON.parse(row.graph) : row.graph,
|
|
668
|
+
}))
|
|
658
669
|
}
|
|
659
670
|
|
|
660
671
|
async close(): Promise<void> {}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|