@pikku/core 0.12.20 → 0.12.22
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 +17 -0
- package/dist/dev/hot-reload.js +20 -6
- package/dist/errors/error-handler.d.ts +1 -1
- package/dist/errors/error-handler.js +2 -2
- package/dist/function/functions.types.d.ts +3 -2
- package/dist/handle-error.js +3 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/middleware/index.d.ts +2 -2
- package/dist/middleware/index.js +2 -2
- package/dist/middleware/telemetry.d.ts +2 -2
- package/dist/middleware/telemetry.js +2 -2
- package/dist/middleware-runner.d.ts +15 -27
- package/dist/middleware-runner.js +25 -30
- package/dist/permissions.d.ts +15 -22
- package/dist/permissions.js +30 -25
- package/dist/pikku-request.js +1 -1
- package/dist/pikku-state.js +2 -3
- package/dist/services/ai-agent-runner-service.d.ts +1 -0
- package/dist/services/in-memory-queue-service.js +1 -1
- package/dist/services/in-memory-workflow-service.d.ts +15 -13
- package/dist/services/in-memory-workflow-service.js +31 -13
- package/dist/services/local-content.js +8 -1
- package/dist/services/user-session-service.d.ts +1 -1
- package/dist/testing/service-tests.js +24 -0
- package/dist/types/core.types.d.ts +4 -0
- package/dist/types/state.types.d.ts +2 -18
- package/dist/wirings/ai-agent/ai-agent-memory.d.ts +24 -5
- package/dist/wirings/ai-agent/ai-agent-memory.js +128 -23
- package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +10 -1
- package/dist/wirings/ai-agent/ai-agent-model-config.js +15 -35
- package/dist/wirings/ai-agent/ai-agent-prepare.js +4 -1
- package/dist/wirings/ai-agent/ai-agent-runner.js +45 -31
- package/dist/wirings/ai-agent/ai-agent-stream.js +63 -34
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +20 -0
- package/dist/wirings/channel/channel-handler.js +1 -1
- package/dist/wirings/channel/channel-store.d.ts +13 -0
- package/dist/wirings/channel/channel.types.d.ts +3 -0
- package/dist/wirings/channel/local/local-channel-runner.js +23 -5
- package/dist/wirings/channel/pikku-abstract-channel-handler.js +8 -0
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +9 -0
- package/dist/wirings/cli/cli-runner.js +24 -5
- package/dist/wirings/cli/command-parser.js +19 -4
- package/dist/wirings/http/http-runner.js +72 -36
- package/dist/wirings/http/http.types.d.ts +1 -0
- package/dist/wirings/http/pikku-fetch-http-request.js +3 -1
- package/dist/wirings/http/web-request.js +32 -0
- package/dist/wirings/rpc/rpc-runner.js +13 -3
- package/dist/wirings/workflow/graph/graph-node.d.ts +8 -0
- package/dist/wirings/workflow/graph/graph-node.js +4 -0
- package/dist/wirings/workflow/graph/graph-runner.js +12 -10
- package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +4 -0
- package/dist/wirings/workflow/index.d.ts +1 -1
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +81 -16
- package/dist/wirings/workflow/pikku-workflow-service.js +266 -45
- package/dist/wirings/workflow/workflow.types.d.ts +34 -0
- package/package.json +1 -1
- package/run-tests.sh +4 -1
- package/src/dev/hot-reload.test.ts +62 -11
- package/src/dev/hot-reload.ts +28 -7
- package/src/errors/error-handler.ts +8 -2
- package/src/errors/error.test.ts +2 -0
- package/src/function/function-runner.test.ts +500 -10
- package/src/function/functions.types.ts +3 -2
- package/src/handle-error.test.ts +1 -1
- package/src/handle-error.ts +4 -1
- package/src/index.ts +12 -2
- package/src/middleware/index.ts +11 -2
- package/src/middleware/telemetry.ts +2 -2
- package/src/middleware-runner.test.ts +16 -16
- package/src/middleware-runner.ts +42 -30
- package/src/permissions.test.ts +27 -24
- package/src/permissions.ts +41 -25
- package/src/pikku-request.test.ts +35 -0
- package/src/pikku-request.ts +1 -1
- package/src/pikku-state.ts +2 -3
- package/src/run-tests-script.test.ts +18 -0
- package/src/services/ai-agent-runner-service.ts +1 -0
- package/src/services/in-memory-queue-service.ts +1 -1
- package/src/services/in-memory-session-store.ts +1 -2
- package/src/services/in-memory-workflow-service.test.ts +33 -11
- package/src/services/in-memory-workflow-service.ts +49 -13
- package/src/services/local-content.test.ts +54 -0
- package/src/services/local-content.ts +12 -1
- package/src/services/typed-credential-service.ts +3 -3
- package/src/services/typed-secret-service.ts +3 -3
- package/src/services/typed-variables-service.ts +3 -3
- package/src/services/user-session-service.ts +4 -4
- package/src/testing/service-tests.ts +30 -0
- package/src/types/core.types.ts +6 -2
- package/src/types/state.types.ts +2 -13
- package/src/wirings/ai-agent/ai-agent-memory.test.ts +324 -0
- package/src/wirings/ai-agent/ai-agent-memory.ts +187 -36
- package/src/wirings/ai-agent/ai-agent-model-config.test.ts +12 -90
- package/src/wirings/ai-agent/ai-agent-model-config.ts +14 -38
- package/src/wirings/ai-agent/ai-agent-prepare.test.ts +292 -0
- package/src/wirings/ai-agent/ai-agent-prepare.ts +4 -1
- package/src/wirings/ai-agent/ai-agent-registry.test.ts +230 -3
- package/src/wirings/ai-agent/ai-agent-runner.test.ts +625 -6
- package/src/wirings/ai-agent/ai-agent-runner.ts +65 -50
- package/src/wirings/ai-agent/ai-agent-stream.test.ts +544 -5
- package/src/wirings/ai-agent/ai-agent-stream.ts +71 -69
- package/src/wirings/ai-agent/ai-agent.types.ts +24 -0
- package/src/wirings/channel/channel-handler.test.ts +272 -0
- package/src/wirings/channel/channel-handler.ts +1 -1
- package/src/wirings/channel/channel-middleware-runner.test.ts +163 -0
- package/src/wirings/channel/channel-store.ts +19 -0
- package/src/wirings/channel/channel.types.ts +4 -0
- package/src/wirings/channel/local/local-channel-runner.test.ts +63 -0
- package/src/wirings/channel/local/local-channel-runner.ts +41 -5
- package/src/wirings/channel/local/local-eventhub-service.ts +3 -3
- package/src/wirings/channel/pikku-abstract-channel-handler.ts +9 -2
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +23 -5
- package/src/wirings/cli/channel/cli-raw-channel-runner.ts +7 -2
- package/src/wirings/cli/cli-runner.test.ts +255 -2
- package/src/wirings/cli/cli-runner.ts +31 -10
- package/src/wirings/cli/cli.types.ts +4 -8
- package/src/wirings/cli/command-parser.test.ts +83 -0
- package/src/wirings/cli/command-parser.ts +23 -4
- package/src/wirings/http/http-runner.test.ts +296 -1
- package/src/wirings/http/http-runner.ts +87 -57
- package/src/wirings/http/http.types.ts +11 -26
- package/src/wirings/http/pikku-fetch-http-request.ts +8 -4
- package/src/wirings/http/pikku-fetch-http-response.test.ts +41 -0
- package/src/wirings/http/web-request.test.ts +115 -0
- package/src/wirings/http/web-request.ts +43 -5
- package/src/wirings/mcp/mcp-runner.test.ts +367 -0
- package/src/wirings/queue/queue.types.ts +2 -5
- package/src/wirings/rpc/rpc-runner.test.ts +511 -0
- package/src/wirings/rpc/rpc-runner.ts +15 -3
- package/src/wirings/rpc/wire-addon.test.ts +57 -0
- package/src/wirings/workflow/graph/graph-node.ts +12 -0
- package/src/wirings/workflow/graph/graph-runner.test.ts +98 -0
- package/src/wirings/workflow/graph/graph-runner.ts +28 -10
- package/src/wirings/workflow/graph/workflow-graph.types.ts +4 -0
- package/src/wirings/workflow/index.ts +1 -0
- package/src/wirings/workflow/pikku-workflow-service.test.ts +19 -2
- package/src/wirings/workflow/pikku-workflow-service.ts +370 -71
- package/src/wirings/workflow/workflow-queue-workers.test.ts +131 -0
- package/src/wirings/workflow/workflow.types.ts +68 -5
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -16,6 +16,7 @@ import { PikkuWorkflowService } from '../wirings/workflow/pikku-workflow-service
|
|
|
16
16
|
* ```
|
|
17
17
|
*/
|
|
18
18
|
export class InMemoryWorkflowService extends PikkuWorkflowService {
|
|
19
|
+
sleepTimers = new Set();
|
|
19
20
|
runs = new Map();
|
|
20
21
|
steps = new Map(); // keyed by `${runId}:${stepName}`
|
|
21
22
|
stepData = new Map(); // keyed by stepId
|
|
@@ -23,7 +24,7 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
|
|
|
23
24
|
runState = new Map(); // keyed by runId
|
|
24
25
|
branchKeys = new Map(); // keyed by stepId
|
|
25
26
|
workflowVersions = new Map(); // keyed by `${name}:${graphHash}`
|
|
26
|
-
async
|
|
27
|
+
async createRunImpl(workflowName, input, inline, graphHash, wire, options) {
|
|
27
28
|
const runId = randomUUID();
|
|
28
29
|
const now = new Date();
|
|
29
30
|
const run = {
|
|
@@ -47,13 +48,26 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
|
|
|
47
48
|
}
|
|
48
49
|
return runId;
|
|
49
50
|
}
|
|
51
|
+
async scheduleSleep(runId, stepId, duration) {
|
|
52
|
+
const timer = setTimeout(async () => {
|
|
53
|
+
this.sleepTimers.delete(timer);
|
|
54
|
+
try {
|
|
55
|
+
await this.executeWorkflowSleepCompleted(runId, stepId);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
this.logger?.error(`Failed to resume workflow sleep for runId ${runId}: ${error?.message ?? error}`);
|
|
59
|
+
}
|
|
60
|
+
}, duration);
|
|
61
|
+
this.sleepTimers.add(timer);
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
50
64
|
async getRun(id) {
|
|
51
65
|
return this.runs.get(id) || null;
|
|
52
66
|
}
|
|
53
67
|
async getRunHistory(runId) {
|
|
54
68
|
return this.stepHistory.get(runId) || [];
|
|
55
69
|
}
|
|
56
|
-
async
|
|
70
|
+
async updateRunStatusImpl(id, status, output, error) {
|
|
57
71
|
const run = this.runs.get(id);
|
|
58
72
|
if (run) {
|
|
59
73
|
run.status = status;
|
|
@@ -64,7 +78,7 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
|
|
|
64
78
|
run.error = error;
|
|
65
79
|
}
|
|
66
80
|
}
|
|
67
|
-
async
|
|
81
|
+
async insertStepStateImpl(runId, stepName, rpcName, data, stepOptions) {
|
|
68
82
|
const stepId = randomUUID();
|
|
69
83
|
const now = new Date();
|
|
70
84
|
const step = {
|
|
@@ -94,7 +108,7 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
|
|
|
94
108
|
}
|
|
95
109
|
return step;
|
|
96
110
|
}
|
|
97
|
-
async
|
|
111
|
+
async setStepRunningImpl(stepId) {
|
|
98
112
|
for (const step of this.steps.values()) {
|
|
99
113
|
if (step.stepId === stepId) {
|
|
100
114
|
step.status = 'running';
|
|
@@ -104,7 +118,7 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
|
|
|
104
118
|
}
|
|
105
119
|
}
|
|
106
120
|
}
|
|
107
|
-
async
|
|
121
|
+
async setStepScheduledImpl(stepId) {
|
|
108
122
|
for (const step of this.steps.values()) {
|
|
109
123
|
if (step.stepId === stepId) {
|
|
110
124
|
step.status = 'scheduled';
|
|
@@ -114,7 +128,7 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
|
|
|
114
128
|
}
|
|
115
129
|
}
|
|
116
130
|
}
|
|
117
|
-
async
|
|
131
|
+
async setStepResultImpl(stepId, result) {
|
|
118
132
|
for (const step of this.steps.values()) {
|
|
119
133
|
if (step.stepId === stepId) {
|
|
120
134
|
step.status = 'succeeded';
|
|
@@ -125,7 +139,7 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
|
|
|
125
139
|
}
|
|
126
140
|
}
|
|
127
141
|
}
|
|
128
|
-
async
|
|
142
|
+
async setStepErrorImpl(stepId, error) {
|
|
129
143
|
for (const step of this.steps.values()) {
|
|
130
144
|
if (step.stepId === stepId) {
|
|
131
145
|
step.status = 'failed';
|
|
@@ -140,7 +154,7 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
|
|
|
140
154
|
}
|
|
141
155
|
}
|
|
142
156
|
}
|
|
143
|
-
async
|
|
157
|
+
async setStepChildRunIdImpl(stepId, childRunId) {
|
|
144
158
|
for (const step of this.steps.values()) {
|
|
145
159
|
if (step.stepId === stepId) {
|
|
146
160
|
step.childRunId = childRunId;
|
|
@@ -149,7 +163,7 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
|
|
|
149
163
|
}
|
|
150
164
|
}
|
|
151
165
|
}
|
|
152
|
-
async
|
|
166
|
+
async createRetryAttemptImpl(failedStepId, status) {
|
|
153
167
|
// Find the failed step
|
|
154
168
|
let failedStep;
|
|
155
169
|
let runId;
|
|
@@ -252,6 +266,10 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
|
|
|
252
266
|
return fn();
|
|
253
267
|
}
|
|
254
268
|
async close() {
|
|
269
|
+
for (const timer of this.sleepTimers) {
|
|
270
|
+
clearTimeout(timer);
|
|
271
|
+
}
|
|
272
|
+
this.sleepTimers.clear();
|
|
255
273
|
// Clear all in-memory state
|
|
256
274
|
this.runs.clear();
|
|
257
275
|
this.steps.clear();
|
|
@@ -317,10 +335,10 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
|
|
|
317
335
|
this.branchKeys.set(step.stepId, branchKey);
|
|
318
336
|
}
|
|
319
337
|
}
|
|
320
|
-
async
|
|
338
|
+
async setBranchTakenImpl(stepId, branchKey) {
|
|
321
339
|
this.branchKeys.set(stepId, branchKey);
|
|
322
340
|
}
|
|
323
|
-
async
|
|
341
|
+
async updateRunStateImpl(runId, name, value) {
|
|
324
342
|
const state = this.runState.get(runId) || {};
|
|
325
343
|
state[name] = value;
|
|
326
344
|
this.runState.set(runId, state);
|
|
@@ -328,14 +346,14 @@ export class InMemoryWorkflowService extends PikkuWorkflowService {
|
|
|
328
346
|
async getRunState(runId) {
|
|
329
347
|
return this.runState.get(runId) || {};
|
|
330
348
|
}
|
|
331
|
-
async
|
|
349
|
+
async upsertWorkflowVersionImpl(name, graphHash, graph, source, status) {
|
|
332
350
|
this.workflowVersions.set(`${name}:${graphHash}`, {
|
|
333
351
|
graph,
|
|
334
352
|
source,
|
|
335
353
|
status: status ?? 'active',
|
|
336
354
|
});
|
|
337
355
|
}
|
|
338
|
-
async
|
|
356
|
+
async updateWorkflowVersionStatusImpl(name, graphHash, status) {
|
|
339
357
|
const key = `${name}:${graphHash}`;
|
|
340
358
|
const version = this.workflowVersions.get(key);
|
|
341
359
|
if (version) {
|
|
@@ -36,7 +36,14 @@ export class LocalContent {
|
|
|
36
36
|
}
|
|
37
37
|
if (this.jwt) {
|
|
38
38
|
const expiresInSeconds = Math.max(1, Math.floor((expiresAt - signedAt) / 1000));
|
|
39
|
-
const
|
|
39
|
+
const payload = {
|
|
40
|
+
signedAt,
|
|
41
|
+
expiresAt,
|
|
42
|
+
};
|
|
43
|
+
if (dateGreaterThan) {
|
|
44
|
+
payload.notBefore = dateGreaterThan.getTime();
|
|
45
|
+
}
|
|
46
|
+
const signature = await this.jwt.encode({ value: expiresInSeconds, unit: 'second' }, payload);
|
|
40
47
|
params.set('signature', signature);
|
|
41
48
|
}
|
|
42
49
|
return params.toString();
|
|
@@ -13,7 +13,7 @@ export declare class PikkuSessionService<UserSession extends CoreUserSession> im
|
|
|
13
13
|
private sessionStore?;
|
|
14
14
|
sessionChanged: boolean;
|
|
15
15
|
initial: UserSession | undefined;
|
|
16
|
-
|
|
16
|
+
protected session: UserSession | undefined;
|
|
17
17
|
private pikkuUserId?;
|
|
18
18
|
constructor(sessionStore?: SessionStore<UserSession> | undefined);
|
|
19
19
|
setPikkuUserId(id: string): void;
|
|
@@ -49,6 +49,30 @@ export function defineServiceTests(config) {
|
|
|
49
49
|
test('removeChannels with empty array is no-op', async () => {
|
|
50
50
|
await store.removeChannels([]);
|
|
51
51
|
});
|
|
52
|
+
test('session round-trip — set / get / clear', async () => {
|
|
53
|
+
await store.addChannel({
|
|
54
|
+
channelId: 'ch-state',
|
|
55
|
+
channelName: 'test-channel',
|
|
56
|
+
});
|
|
57
|
+
// initially undefined
|
|
58
|
+
const empty = await store.getState('ch-state');
|
|
59
|
+
assert.equal(empty, undefined);
|
|
60
|
+
// set + get
|
|
61
|
+
const payload = { userId: 'u-7', meta: { foo: 1 } };
|
|
62
|
+
await store.setState('ch-state', payload);
|
|
63
|
+
const got = await store.getState('ch-state');
|
|
64
|
+
assert.deepEqual(got, payload);
|
|
65
|
+
// overwrite
|
|
66
|
+
const next = { userId: 'u-8' };
|
|
67
|
+
await store.setState('ch-state', next);
|
|
68
|
+
const got2 = await store.getState('ch-state');
|
|
69
|
+
assert.deepEqual(got2, next);
|
|
70
|
+
// clear
|
|
71
|
+
await store.clearState('ch-state');
|
|
72
|
+
const after = await store.getState('ch-state');
|
|
73
|
+
assert.equal(after, undefined);
|
|
74
|
+
await store.removeChannels(['ch-state']);
|
|
75
|
+
});
|
|
52
76
|
});
|
|
53
77
|
}
|
|
54
78
|
if (services.eventHubStore) {
|
|
@@ -5,6 +5,7 @@ import type { SchemaService } from '../services/schema-service.js';
|
|
|
5
5
|
import type { JWTService } from '../services/jwt-service.js';
|
|
6
6
|
import type { PikkuHTTP } from '../wirings/http/http.types.js';
|
|
7
7
|
import type { PikkuChannel, CorePikkuChannelMiddleware, CorePikkuChannelMiddlewareFactory } from '../wirings/channel/channel.types.js';
|
|
8
|
+
import type { EventHubService } from '../wirings/channel/eventhub-service.js';
|
|
8
9
|
import type { PikkuRPC } from '../wirings/rpc/rpc-types.js';
|
|
9
10
|
import type { PikkuMCP } from '../wirings/mcp/mcp.types.js';
|
|
10
11
|
import type { PikkuScheduledTask } from '../wirings/scheduler/scheduler.types.js';
|
|
@@ -82,6 +83,7 @@ export type FunctionRuntimeMeta = {
|
|
|
82
83
|
version?: number;
|
|
83
84
|
approvalRequired?: boolean;
|
|
84
85
|
approvalDescription?: string;
|
|
86
|
+
implementationHash?: string;
|
|
85
87
|
contractHash?: string;
|
|
86
88
|
inputHash?: string;
|
|
87
89
|
outputHash?: string;
|
|
@@ -162,6 +164,8 @@ export interface CoreSingletonServices<Config extends CoreConfig = CoreConfig> {
|
|
|
162
164
|
workflowService?: WorkflowService;
|
|
163
165
|
/** The queue service */
|
|
164
166
|
queueService?: QueueService;
|
|
167
|
+
/** Event hub for realtime pub/sub across channels */
|
|
168
|
+
eventHub?: EventHubService<Record<string, any>>;
|
|
165
169
|
/** The scheduler service */
|
|
166
170
|
schedulerService?: SchedulerService;
|
|
167
171
|
/** The deployment service for service discovery */
|
|
@@ -88,6 +88,7 @@ export interface PikkuPackageState {
|
|
|
88
88
|
middleware: {
|
|
89
89
|
tagGroup: Record<string, CorePikkuMiddlewareGroup>;
|
|
90
90
|
httpGroup: Record<string, CorePikkuMiddlewareGroup>;
|
|
91
|
+
global: CorePikkuMiddlewareGroup;
|
|
91
92
|
};
|
|
92
93
|
channelMiddleware: {
|
|
93
94
|
tagGroup: Record<string, CorePikkuChannelMiddleware[]>;
|
|
@@ -95,6 +96,7 @@ export interface PikkuPackageState {
|
|
|
95
96
|
permissions: {
|
|
96
97
|
tagGroup: Record<string, CorePermissionGroup | CorePikkuPermission[]>;
|
|
97
98
|
httpGroup: Record<string, CorePermissionGroup | CorePikkuPermission[]>;
|
|
99
|
+
global: (CorePermissionGroup | CorePikkuPermission)[];
|
|
98
100
|
};
|
|
99
101
|
misc: {
|
|
100
102
|
errors: Map<PikkuError, ErrorDetails>;
|
|
@@ -103,24 +105,6 @@ export interface PikkuPackageState {
|
|
|
103
105
|
channelMiddleware: Record<string, CorePikkuChannelMiddleware[]>;
|
|
104
106
|
permissions: Record<string, CorePermissionGroup | CorePikkuPermission[]>;
|
|
105
107
|
};
|
|
106
|
-
models: {
|
|
107
|
-
config: {
|
|
108
|
-
models?: Record<string, string | {
|
|
109
|
-
model: string;
|
|
110
|
-
temperature?: number;
|
|
111
|
-
maxSteps?: number;
|
|
112
|
-
}>;
|
|
113
|
-
agentDefaults?: {
|
|
114
|
-
temperature?: number;
|
|
115
|
-
maxSteps?: number;
|
|
116
|
-
};
|
|
117
|
-
agentOverrides?: Record<string, {
|
|
118
|
-
model?: string;
|
|
119
|
-
temperature?: number;
|
|
120
|
-
maxSteps?: number;
|
|
121
|
-
}>;
|
|
122
|
-
} | null;
|
|
123
|
-
};
|
|
124
108
|
package: {
|
|
125
109
|
/** Service factory functions for addon packages */
|
|
126
110
|
factories: {
|
|
@@ -3,14 +3,17 @@ import type { CoreAIAgent, AIAgentMemoryConfig, AIAgentInput, AIMessage } from '
|
|
|
3
3
|
import type { AIStorageService } from '../../services/ai-storage-service.js';
|
|
4
4
|
import type { Logger } from '../../services/logger.js';
|
|
5
5
|
import type { SchemaService } from '../../services/schema-service.js';
|
|
6
|
+
import type { PikkuAIMiddlewareHooks } from './ai-agent.types.js';
|
|
6
7
|
export declare function resolveMemoryServices(agent: CoreAIAgent, singletonServices: CoreSingletonServices): {
|
|
7
8
|
storage: AIStorageService | undefined;
|
|
8
9
|
};
|
|
10
|
+
export declare function isWorkingMemoryEnabled(memoryConfig: AIAgentMemoryConfig | undefined, storage: AIStorageService | undefined): boolean;
|
|
9
11
|
export declare function deepMergeWorkingMemory(existing: Record<string, unknown>, updates: Record<string, unknown>): Record<string, unknown>;
|
|
10
12
|
export declare function buildWorkingMemoryPrompt(currentState: Record<string, unknown> | null, jsonSchema?: Record<string, unknown>): string;
|
|
11
13
|
export declare function loadContextMessages(memoryConfig: AIAgentMemoryConfig | undefined, storage: AIStorageService | undefined, input: AIAgentInput, workingMemoryJsonSchema?: Record<string, unknown>): Promise<AIMessage[]>;
|
|
12
14
|
export declare function saveMessages(storage: AIStorageService | undefined, threadId: string, resourceId: string, memoryConfig: AIAgentMemoryConfig | undefined, userMessage: AIMessage | null | undefined, result: {
|
|
13
15
|
text: string;
|
|
16
|
+
uiSpec?: unknown;
|
|
14
17
|
steps: {
|
|
15
18
|
toolCalls?: {
|
|
16
19
|
name: string;
|
|
@@ -18,12 +21,28 @@ export declare function saveMessages(storage: AIStorageService | undefined, thre
|
|
|
18
21
|
result: string;
|
|
19
22
|
}[];
|
|
20
23
|
}[];
|
|
21
|
-
}, options?: {
|
|
22
|
-
workingMemoryJsonSchema?: Record<string, unknown>;
|
|
23
|
-
workingMemorySchemaName?: string | null;
|
|
24
|
-
logger?: Logger;
|
|
25
|
-
schemaService?: SchemaService;
|
|
26
24
|
}): Promise<string>;
|
|
27
25
|
export declare function trimMessages(messages: AIMessage[], maxTokenBudget?: number): AIMessage[];
|
|
28
26
|
export declare function parseWorkingMemory(text: string): Record<string, unknown> | null;
|
|
29
27
|
export declare function stripWorkingMemoryTags(text: string): string;
|
|
28
|
+
export declare function extractWorkingMemory(text: string): {
|
|
29
|
+
workingMemory: Record<string, unknown> | null;
|
|
30
|
+
cleanedText: string;
|
|
31
|
+
};
|
|
32
|
+
export declare function stripWorkingMemoryForStreaming(text: string): string;
|
|
33
|
+
export declare function createWorkingMemoryMiddleware(options: {
|
|
34
|
+
storage?: AIStorageService;
|
|
35
|
+
threadId: string;
|
|
36
|
+
workingMemorySchemaName?: string | null;
|
|
37
|
+
logger?: Logger;
|
|
38
|
+
schemaService?: SchemaService;
|
|
39
|
+
}): PikkuAIMiddlewareHooks<{
|
|
40
|
+
rawText?: string;
|
|
41
|
+
emittedVisibleText?: string;
|
|
42
|
+
}>;
|
|
43
|
+
export declare function getWorkingMemoryMiddleware(memoryConfig: AIAgentMemoryConfig | undefined, storage: AIStorageService | undefined, options: {
|
|
44
|
+
threadId: string;
|
|
45
|
+
workingMemorySchemaName?: string | null;
|
|
46
|
+
logger?: Logger;
|
|
47
|
+
schemaService?: SchemaService;
|
|
48
|
+
}): PikkuAIMiddlewareHooks[];
|
|
@@ -6,9 +6,15 @@ export function resolveMemoryServices(agent, singletonServices) {
|
|
|
6
6
|
: singletonServices.aiStorage;
|
|
7
7
|
return { storage };
|
|
8
8
|
}
|
|
9
|
+
export function isWorkingMemoryEnabled(memoryConfig, storage) {
|
|
10
|
+
return !!memoryConfig?.workingMemory && !!storage;
|
|
11
|
+
}
|
|
9
12
|
export function deepMergeWorkingMemory(existing, updates) {
|
|
10
13
|
const result = { ...existing };
|
|
11
14
|
for (const key of Object.keys(updates)) {
|
|
15
|
+
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
12
18
|
const value = updates[key];
|
|
13
19
|
if (value === null) {
|
|
14
20
|
delete result[key];
|
|
@@ -46,13 +52,18 @@ export function buildWorkingMemoryPrompt(currentState, jsonSchema) {
|
|
|
46
52
|
parts.push('Current working memory: (empty)');
|
|
47
53
|
}
|
|
48
54
|
parts.push('When you learn new information, output a partial JSON update in <working_memory> tags. ' +
|
|
49
|
-
'Only include
|
|
55
|
+
'Only include durable facts you have actually derived or the user has confirmed. ' +
|
|
56
|
+
'Do not output templates, placeholders, or narration. ' +
|
|
57
|
+
'Only include changed fields. Leave unknown fields untouched. Set a field to null to delete it.');
|
|
50
58
|
return parts.join('\n\n');
|
|
51
59
|
}
|
|
52
60
|
export async function loadContextMessages(memoryConfig, storage, input, workingMemoryJsonSchema) {
|
|
53
61
|
const contextMessages = [];
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
const workingMemoryStorage = isWorkingMemoryEnabled(memoryConfig, storage)
|
|
63
|
+
? storage
|
|
64
|
+
: undefined;
|
|
65
|
+
if (workingMemoryStorage) {
|
|
66
|
+
const workingMem = await workingMemoryStorage.getWorkingMemory(input.threadId, 'thread');
|
|
56
67
|
const prompt = buildWorkingMemoryPrompt(workingMem, workingMemoryJsonSchema);
|
|
57
68
|
contextMessages.push({
|
|
58
69
|
id: `wm-${randomUUID()}`,
|
|
@@ -63,8 +74,10 @@ export async function loadContextMessages(memoryConfig, storage, input, workingM
|
|
|
63
74
|
}
|
|
64
75
|
return contextMessages;
|
|
65
76
|
}
|
|
66
|
-
export async function saveMessages(storage, threadId, resourceId, memoryConfig, userMessage, result
|
|
67
|
-
|
|
77
|
+
export async function saveMessages(storage, threadId, resourceId, memoryConfig, userMessage, result) {
|
|
78
|
+
const responseText = memoryConfig?.workingMemory
|
|
79
|
+
? extractWorkingMemory(result.text).cleanedText
|
|
80
|
+
: result.text;
|
|
68
81
|
if (storage) {
|
|
69
82
|
const newMessages = userMessage ? [userMessage] : [];
|
|
70
83
|
for (const step of result.steps) {
|
|
@@ -92,30 +105,21 @@ export async function saveMessages(storage, threadId, resourceId, memoryConfig,
|
|
|
92
105
|
});
|
|
93
106
|
}
|
|
94
107
|
}
|
|
108
|
+
const assistantContent = result.uiSpec != null
|
|
109
|
+
? [
|
|
110
|
+
...(responseText
|
|
111
|
+
? [{ type: 'text', text: responseText }]
|
|
112
|
+
: []),
|
|
113
|
+
{ type: 'generative-ui', spec: result.uiSpec },
|
|
114
|
+
]
|
|
115
|
+
: responseText;
|
|
95
116
|
newMessages.push({
|
|
96
117
|
id: randomUUID(),
|
|
97
118
|
role: 'assistant',
|
|
98
|
-
content:
|
|
119
|
+
content: assistantContent || undefined,
|
|
99
120
|
createdAt: new Date(),
|
|
100
121
|
});
|
|
101
122
|
await storage.saveMessages(threadId, newMessages);
|
|
102
|
-
if (memoryConfig?.workingMemory) {
|
|
103
|
-
const parsed = parseWorkingMemory(responseText);
|
|
104
|
-
if (parsed) {
|
|
105
|
-
const existing = (await storage.getWorkingMemory(threadId, 'thread')) ?? {};
|
|
106
|
-
const merged = deepMergeWorkingMemory(existing, parsed);
|
|
107
|
-
if (options?.schemaService && options?.workingMemorySchemaName) {
|
|
108
|
-
try {
|
|
109
|
-
await options.schemaService.validateSchema(options.workingMemorySchemaName, merged);
|
|
110
|
-
}
|
|
111
|
-
catch (err) {
|
|
112
|
-
options.logger?.warn(`Working memory validation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
await storage.saveWorkingMemory(threadId, 'thread', merged);
|
|
116
|
-
responseText = stripWorkingMemoryTags(responseText);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
123
|
}
|
|
120
124
|
return responseText;
|
|
121
125
|
}
|
|
@@ -209,3 +213,104 @@ export function parseWorkingMemory(text) {
|
|
|
209
213
|
export function stripWorkingMemoryTags(text) {
|
|
210
214
|
return text.replace(/<working_memory>[\s\S]*?<\/working_memory>/g, '').trim();
|
|
211
215
|
}
|
|
216
|
+
export function extractWorkingMemory(text) {
|
|
217
|
+
const workingMemory = parseWorkingMemory(text);
|
|
218
|
+
return {
|
|
219
|
+
workingMemory,
|
|
220
|
+
cleanedText: workingMemory ? stripWorkingMemoryTags(text) : text,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function getPendingWorkingMemoryPrefixLength(text) {
|
|
224
|
+
const prefixes = ['<working_memory>', '</working_memory>'];
|
|
225
|
+
const lastLt = text.lastIndexOf('<');
|
|
226
|
+
if (lastLt === -1)
|
|
227
|
+
return 0;
|
|
228
|
+
const suffix = text.slice(lastLt);
|
|
229
|
+
for (const prefix of prefixes) {
|
|
230
|
+
if (prefix.startsWith(suffix)) {
|
|
231
|
+
return suffix.length;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return 0;
|
|
235
|
+
}
|
|
236
|
+
export function stripWorkingMemoryForStreaming(text) {
|
|
237
|
+
let output = '';
|
|
238
|
+
let cursor = 0;
|
|
239
|
+
while (cursor < text.length) {
|
|
240
|
+
const openIndex = text.indexOf('<working_memory>', cursor);
|
|
241
|
+
if (openIndex === -1) {
|
|
242
|
+
const tail = text.slice(cursor);
|
|
243
|
+
const pendingPrefixLength = getPendingWorkingMemoryPrefixLength(tail);
|
|
244
|
+
output +=
|
|
245
|
+
pendingPrefixLength > 0 ? tail.slice(0, -pendingPrefixLength) : tail;
|
|
246
|
+
return output;
|
|
247
|
+
}
|
|
248
|
+
output += text.slice(cursor, openIndex);
|
|
249
|
+
const closeIndex = text.indexOf('</working_memory>', openIndex);
|
|
250
|
+
if (closeIndex === -1) {
|
|
251
|
+
return output;
|
|
252
|
+
}
|
|
253
|
+
cursor = closeIndex + '</working_memory>'.length;
|
|
254
|
+
}
|
|
255
|
+
return output;
|
|
256
|
+
}
|
|
257
|
+
export function createWorkingMemoryMiddleware(options) {
|
|
258
|
+
return {
|
|
259
|
+
modifyOutputStream: async (_services, { event, state }) => {
|
|
260
|
+
if (event.type !== 'text-delta')
|
|
261
|
+
return event;
|
|
262
|
+
const rawText = `${state.rawText ?? ''}${event.text}`;
|
|
263
|
+
state.rawText = rawText;
|
|
264
|
+
const visibleText = stripWorkingMemoryForStreaming(rawText);
|
|
265
|
+
const emittedVisibleText = state.emittedVisibleText ?? '';
|
|
266
|
+
if (!visibleText.startsWith(emittedVisibleText)) {
|
|
267
|
+
state.emittedVisibleText = visibleText;
|
|
268
|
+
return { ...event, text: visibleText };
|
|
269
|
+
}
|
|
270
|
+
const delta = visibleText.slice(emittedVisibleText.length);
|
|
271
|
+
state.emittedVisibleText = visibleText;
|
|
272
|
+
if (!delta)
|
|
273
|
+
return null;
|
|
274
|
+
return { ...event, text: delta };
|
|
275
|
+
},
|
|
276
|
+
modifyOutput: async (_services, { text, messages, usage }) => {
|
|
277
|
+
const { workingMemory, cleanedText } = extractWorkingMemory(text);
|
|
278
|
+
if (workingMemory && options.storage) {
|
|
279
|
+
const existing = (await options.storage.getWorkingMemory(options.threadId, 'thread')) ?? {};
|
|
280
|
+
const merged = deepMergeWorkingMemory(existing, workingMemory);
|
|
281
|
+
let valid = true;
|
|
282
|
+
if (options.schemaService && options.workingMemorySchemaName) {
|
|
283
|
+
try {
|
|
284
|
+
await options.schemaService.validateSchema(options.workingMemorySchemaName, merged);
|
|
285
|
+
}
|
|
286
|
+
catch (err) {
|
|
287
|
+
valid = false;
|
|
288
|
+
options.logger?.warn(`Working memory validation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// Only persist when the merged value passes schema validation —
|
|
292
|
+
// saving invalid data would poison subsequent getWorkingMemory reads.
|
|
293
|
+
if (valid) {
|
|
294
|
+
await options.storage.saveWorkingMemory(options.threadId, 'thread', merged);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
text: cleanedText,
|
|
299
|
+
messages,
|
|
300
|
+
};
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
export function getWorkingMemoryMiddleware(memoryConfig, storage, options) {
|
|
305
|
+
if (!isWorkingMemoryEnabled(memoryConfig, storage))
|
|
306
|
+
return [];
|
|
307
|
+
return [
|
|
308
|
+
createWorkingMemoryMiddleware({
|
|
309
|
+
storage,
|
|
310
|
+
threadId: options.threadId,
|
|
311
|
+
workingMemorySchemaName: options.workingMemorySchemaName,
|
|
312
|
+
logger: options.logger,
|
|
313
|
+
schemaService: options.schemaService,
|
|
314
|
+
}),
|
|
315
|
+
];
|
|
316
|
+
}
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the effective model/temperature/maxSteps for an agent at runtime.
|
|
3
|
+
*
|
|
4
|
+
* Models are declared per-agent using the provider-qualified `provider/model`
|
|
5
|
+
* form (e.g. `openai/gpt-5-mini`); there is no config-level alias map. This
|
|
6
|
+
* function remains the single merge point for request-time overrides (see
|
|
7
|
+
* ai-agent-prepare's `input.model`), so callers stay decoupled from how the
|
|
8
|
+
* effective config is assembled.
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveModelConfig(_agentName: string, agent: {
|
|
2
11
|
model: string;
|
|
3
12
|
temperature?: number;
|
|
4
13
|
maxSteps?: number;
|
|
@@ -1,36 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (!entry)
|
|
17
|
-
throw new Error(`Unknown model alias '${rawModel}'.`);
|
|
18
|
-
if (typeof entry === 'string') {
|
|
19
|
-
rawModel = entry;
|
|
20
|
-
}
|
|
21
|
-
else {
|
|
22
|
-
rawModel = entry.model;
|
|
23
|
-
aliasTemperature = entry.temperature;
|
|
24
|
-
aliasMaxSteps = entry.maxSteps;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
const temperature = agentOverride?.temperature ??
|
|
28
|
-
aliasTemperature ??
|
|
29
|
-
defaults?.temperature ??
|
|
30
|
-
agent.temperature;
|
|
31
|
-
const maxSteps = agentOverride?.maxSteps ??
|
|
32
|
-
aliasMaxSteps ??
|
|
33
|
-
defaults?.maxSteps ??
|
|
34
|
-
agent.maxSteps;
|
|
35
|
-
return { model: rawModel, temperature, maxSteps };
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the effective model/temperature/maxSteps for an agent at runtime.
|
|
3
|
+
*
|
|
4
|
+
* Models are declared per-agent using the provider-qualified `provider/model`
|
|
5
|
+
* form (e.g. `openai/gpt-5-mini`); there is no config-level alias map. This
|
|
6
|
+
* function remains the single merge point for request-time overrides (see
|
|
7
|
+
* ai-agent-prepare's `input.model`), so callers stay decoupled from how the
|
|
8
|
+
* effective config is assembled.
|
|
9
|
+
*/
|
|
10
|
+
export function resolveModelConfig(_agentName, agent) {
|
|
11
|
+
return {
|
|
12
|
+
model: agent.model,
|
|
13
|
+
temperature: agent.temperature,
|
|
14
|
+
maxSteps: agent.maxSteps,
|
|
15
|
+
};
|
|
36
16
|
}
|
|
@@ -169,6 +169,9 @@ export function createScopedChannel(parent, agentName, session) {
|
|
|
169
169
|
parent.send(event);
|
|
170
170
|
}
|
|
171
171
|
},
|
|
172
|
+
setState: (s) => parent.setState(s),
|
|
173
|
+
getState: () => parent.getState(),
|
|
174
|
+
clearState: () => parent.clearState(),
|
|
172
175
|
};
|
|
173
176
|
}
|
|
174
177
|
export async function buildToolDefs(params, agentSessionMap, resourceId, agentName, packageName, streamContext, aiMiddlewares, agentMode) {
|
|
@@ -254,7 +257,7 @@ export async function buildToolDefs(params, agentSessionMap, resourceId, agentNa
|
|
|
254
257
|
}
|
|
255
258
|
}
|
|
256
259
|
tools.push({
|
|
257
|
-
name: toolName.replaceAll(':', '__'),
|
|
260
|
+
name: toolName.replaceAll('@', '_').replaceAll(':', '__'),
|
|
258
261
|
description: fnMeta?.description || fnMeta?.title || toolName,
|
|
259
262
|
inputSchema,
|
|
260
263
|
needsApproval: needsApproval || undefined,
|