@pikku/core 0.12.1 → 0.12.3
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 +34 -0
- package/dist/crypto-utils.d.ts +10 -0
- package/dist/crypto-utils.js +62 -0
- package/dist/function/function-runner.js +4 -4
- package/dist/function/functions.types.d.ts +26 -1
- package/dist/function/functions.types.js +16 -0
- package/dist/handle-error.d.ts +1 -1
- package/dist/handle-error.js +7 -3
- package/dist/index.d.ts +14 -2
- package/dist/index.js +1 -1
- package/dist/middleware-runner.js +1 -0
- package/dist/permissions.js +1 -0
- package/dist/pikku-state.js +7 -1
- package/dist/schema.js +1 -1
- package/dist/services/gateway-service.d.ts +19 -0
- package/dist/services/gateway-service.js +1 -0
- package/dist/services/in-memory-workflow-service.d.ts +8 -2
- package/dist/services/in-memory-workflow-service.js +28 -1
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.js +1 -0
- package/dist/services/local-gateway-service.d.ts +22 -0
- package/dist/services/local-gateway-service.js +51 -0
- package/dist/services/workflow-service.d.ts +8 -2
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +1 -0
- package/dist/testing/service-tests.d.ts +31 -0
- package/dist/testing/service-tests.js +598 -0
- package/dist/types/core.types.d.ts +12 -2
- package/dist/types/state.types.d.ts +9 -2
- package/dist/utils/hash.d.ts +2 -0
- package/dist/utils/hash.js +23 -0
- package/dist/utils.js +1 -1
- package/dist/wirings/ai-agent/agent-dynamic-workflow.d.ts +6 -0
- package/dist/wirings/ai-agent/agent-dynamic-workflow.js +348 -0
- package/dist/wirings/ai-agent/ai-agent-memory.d.ts +1 -1
- package/dist/wirings/ai-agent/ai-agent-memory.js +19 -7
- package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +10 -7
- package/dist/wirings/ai-agent/ai-agent-prepare.js +141 -48
- package/dist/wirings/ai-agent/ai-agent-runner.d.ts +4 -0
- package/dist/wirings/ai-agent/ai-agent-runner.js +381 -27
- package/dist/wirings/ai-agent/ai-agent-stream.d.ts +7 -2
- package/dist/wirings/ai-agent/ai-agent-stream.js +126 -48
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +57 -3
- package/dist/wirings/ai-agent/index.d.ts +2 -3
- package/dist/wirings/ai-agent/index.js +1 -2
- package/dist/wirings/channel/channel-handler.d.ts +5 -2
- package/dist/wirings/channel/channel-handler.js +16 -2
- package/dist/wirings/channel/channel-middleware-runner.js +1 -0
- package/dist/wirings/channel/channel.types.d.ts +7 -7
- package/dist/wirings/channel/index.d.ts +1 -1
- package/dist/wirings/channel/local/local-channel-handler.d.ts +8 -1
- package/dist/wirings/channel/local/local-channel-handler.js +22 -3
- package/dist/wirings/channel/local/local-channel-runner.js +28 -5
- package/dist/wirings/channel/pikku-abstract-channel-handler.d.ts +2 -1
- package/dist/wirings/channel/pikku-abstract-channel-handler.js +1 -0
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +5 -2
- package/dist/wirings/cli/cli-runner.js +13 -2
- package/dist/wirings/cli/cli.types.d.ts +1 -0
- package/dist/wirings/gateway/gateway-runner.d.ts +24 -0
- package/dist/wirings/gateway/gateway-runner.js +325 -0
- package/dist/wirings/gateway/gateway.types.d.ts +127 -0
- package/dist/wirings/gateway/gateway.types.js +1 -0
- package/dist/wirings/gateway/index.d.ts +2 -0
- package/dist/wirings/gateway/index.js +1 -0
- package/dist/wirings/http/http-routes.js +0 -1
- package/dist/wirings/http/http-runner.d.ts +1 -1
- package/dist/wirings/http/http-runner.js +28 -3
- package/dist/wirings/http/http.types.d.ts +3 -11
- package/dist/wirings/http/index.d.ts +2 -1
- package/dist/wirings/http/index.js +1 -0
- package/dist/wirings/http/pikku-fetch-http-request.d.ts +1 -0
- package/dist/wirings/http/pikku-fetch-http-request.js +3 -0
- package/dist/wirings/http/pikku-fetch-http-response.d.ts +1 -0
- package/dist/wirings/http/pikku-fetch-http-response.js +4 -4
- package/dist/wirings/http/web-request.d.ts +12 -0
- package/dist/wirings/http/web-request.js +85 -0
- package/dist/wirings/mcp/mcp-endpoint-registry.js +1 -1
- package/dist/wirings/mcp/mcp-runner.js +13 -1
- package/dist/wirings/rpc/rpc-runner.d.ts +30 -6
- package/dist/wirings/rpc/rpc-runner.js +36 -14
- package/dist/wirings/rpc/rpc-types.d.ts +6 -1
- package/dist/wirings/rpc/wire-addon.d.ts +1 -0
- package/dist/wirings/rpc/wire-addon.js +1 -1
- package/dist/wirings/scheduler/scheduler-runner.js +1 -0
- package/dist/wirings/workflow/graph/graph-validation.d.ts +3 -0
- package/dist/wirings/workflow/graph/graph-validation.js +175 -0
- package/dist/wirings/workflow/graph/index.d.ts +1 -0
- package/dist/wirings/workflow/graph/index.js +1 -0
- package/dist/wirings/workflow/index.d.ts +1 -1
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +8 -2
- package/dist/wirings/workflow/pikku-workflow-service.js +1 -2
- package/dist/wirings/workflow/workflow.types.d.ts +10 -1
- package/package.json +6 -3
- package/src/crypto-utils.test.ts +99 -1
- package/src/crypto-utils.ts +114 -0
- package/src/function/function-runner.ts +5 -5
- package/src/function/functions.types.ts +38 -1
- package/src/handle-error.test.ts +65 -2
- package/src/handle-error.ts +8 -4
- package/src/index.ts +14 -0
- package/src/middleware-runner.ts +1 -0
- package/src/permissions.ts +1 -0
- package/src/pikku-state.ts +8 -1
- package/src/schema.ts +1 -1
- package/src/services/gateway-service.ts +20 -0
- package/src/services/in-memory-workflow-service.ts +45 -2
- package/src/services/index.ts +2 -0
- package/src/services/local-gateway-service.ts +62 -0
- package/src/services/workflow-service.ts +12 -1
- package/src/testing/index.ts +2 -0
- package/src/testing/service-tests.ts +873 -0
- package/src/types/core.types.ts +12 -1
- package/src/types/state.types.ts +12 -2
- package/src/utils/hash.test.ts +68 -0
- package/src/utils/hash.ts +26 -0
- package/src/utils.ts +1 -1
- package/src/wirings/ai-agent/agent-dynamic-workflow.ts +469 -0
- package/src/wirings/ai-agent/ai-agent-memory.ts +17 -7
- package/src/wirings/ai-agent/ai-agent-prepare.ts +194 -62
- package/src/wirings/ai-agent/ai-agent-runner.test.ts +3 -1
- package/src/wirings/ai-agent/ai-agent-runner.ts +553 -31
- package/src/wirings/ai-agent/ai-agent-stream.test.ts +209 -2
- package/src/wirings/ai-agent/ai-agent-stream.ts +195 -91
- package/src/wirings/ai-agent/ai-agent.types.ts +57 -3
- package/src/wirings/ai-agent/index.ts +3 -5
- package/src/wirings/channel/channel-handler.ts +41 -4
- package/src/wirings/channel/channel-middleware-runner.ts +1 -0
- package/src/wirings/channel/channel.types.ts +12 -7
- package/src/wirings/channel/index.ts +1 -0
- package/src/wirings/channel/local/local-channel-handler.ts +31 -3
- package/src/wirings/channel/local/local-channel-runner.ts +32 -8
- package/src/wirings/channel/pikku-abstract-channel-handler.test.ts +2 -0
- package/src/wirings/channel/pikku-abstract-channel-handler.ts +8 -1
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +5 -2
- package/src/wirings/cli/cli-runner.ts +15 -2
- package/src/wirings/cli/cli.types.ts +1 -0
- package/src/wirings/gateway/gateway-runner.test.ts +474 -0
- package/src/wirings/gateway/gateway-runner.ts +411 -0
- package/src/wirings/gateway/gateway.types.ts +149 -0
- package/src/wirings/gateway/index.ts +13 -0
- package/src/wirings/http/http-routes.ts +0 -1
- package/src/wirings/http/http-runner.ts +38 -10
- package/src/wirings/http/http.types.ts +3 -12
- package/src/wirings/http/index.ts +3 -0
- package/src/wirings/http/pikku-fetch-http-request.ts +4 -0
- package/src/wirings/http/pikku-fetch-http-response.ts +4 -4
- package/src/wirings/http/web-request.test.ts +236 -0
- package/src/wirings/http/web-request.ts +104 -0
- package/src/wirings/mcp/mcp-endpoint-registry.ts +1 -1
- package/src/wirings/mcp/mcp-runner.ts +30 -12
- package/src/wirings/rpc/rpc-runner.ts +45 -24
- package/src/wirings/rpc/rpc-types.ts +6 -1
- package/src/wirings/rpc/wire-addon.ts +2 -1
- package/src/wirings/scheduler/scheduler-runner.ts +1 -0
- package/src/wirings/workflow/graph/graph-validation.test.ts +170 -0
- package/src/wirings/workflow/graph/graph-validation.ts +237 -0
- package/src/wirings/workflow/graph/index.ts +5 -0
- package/src/wirings/workflow/index.ts +1 -0
- package/src/wirings/workflow/pikku-workflow-service.ts +14 -3
- package/src/wirings/workflow/workflow.types.ts +9 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/wirings/ai-agent/ai-agent-assistant-ui.d.ts +0 -5
- package/dist/wirings/ai-agent/ai-agent-assistant-ui.js +0 -146
- package/src/wirings/ai-agent/ai-agent-assistant-ui.test.ts +0 -363
- package/src/wirings/ai-agent/ai-agent-assistant-ui.ts +0 -214
|
@@ -1,15 +1,22 @@
|
|
|
1
|
+
import type { BinaryData } from '../channel.types.js';
|
|
1
2
|
import { PikkuAbstractChannelHandler } from '../pikku-abstract-channel-handler.js';
|
|
2
3
|
export declare class PikkuLocalChannelHandler<OpeningData = unknown, Out = unknown> extends PikkuAbstractChannelHandler<OpeningData, Out> {
|
|
3
4
|
private onMessageCallback?;
|
|
5
|
+
private onBinaryMessageCallback?;
|
|
4
6
|
private openCallBack?;
|
|
5
|
-
private
|
|
7
|
+
private closeCallbacks;
|
|
6
8
|
private sendCallback?;
|
|
9
|
+
private sendBinaryCallback?;
|
|
7
10
|
registerOnOpen(callback: () => void): void;
|
|
8
11
|
open(): void;
|
|
9
12
|
registerOnMessage(callback: (data: any) => Promise<unknown>): void;
|
|
10
13
|
message(data: unknown): Promise<unknown>;
|
|
14
|
+
registerOnBinaryMessage(callback: (data: BinaryData) => Promise<BinaryData | void> | BinaryData | void): void;
|
|
15
|
+
binaryMessage(data: BinaryData): Promise<BinaryData | void>;
|
|
11
16
|
registerOnClose(callback: () => void): void;
|
|
12
17
|
close(): void;
|
|
13
18
|
registerOnSend(send: (message: Out) => void): void;
|
|
14
19
|
send(message: Out, isBinary?: boolean): void;
|
|
20
|
+
registerOnSendBinary(send: (data: BinaryData) => void): void;
|
|
21
|
+
sendBinary(data: BinaryData): void;
|
|
15
22
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { PikkuAbstractChannelHandler } from '../pikku-abstract-channel-handler.js';
|
|
2
2
|
export class PikkuLocalChannelHandler extends PikkuAbstractChannelHandler {
|
|
3
3
|
onMessageCallback;
|
|
4
|
+
onBinaryMessageCallback;
|
|
4
5
|
openCallBack;
|
|
5
|
-
|
|
6
|
+
closeCallbacks = [];
|
|
6
7
|
sendCallback;
|
|
8
|
+
sendBinaryCallback;
|
|
7
9
|
registerOnOpen(callback) {
|
|
8
10
|
this.openCallBack = callback;
|
|
9
11
|
}
|
|
@@ -19,15 +21,23 @@ export class PikkuLocalChannelHandler extends PikkuAbstractChannelHandler {
|
|
|
19
21
|
async message(data) {
|
|
20
22
|
return this.onMessageCallback?.(data);
|
|
21
23
|
}
|
|
24
|
+
registerOnBinaryMessage(callback) {
|
|
25
|
+
this.onBinaryMessageCallback = callback;
|
|
26
|
+
}
|
|
27
|
+
async binaryMessage(data) {
|
|
28
|
+
return this.onBinaryMessageCallback?.(data);
|
|
29
|
+
}
|
|
22
30
|
registerOnClose(callback) {
|
|
23
|
-
this.
|
|
31
|
+
this.closeCallbacks.push(callback);
|
|
24
32
|
}
|
|
25
33
|
close() {
|
|
26
34
|
if (this.getChannel().state === 'closed') {
|
|
27
35
|
return;
|
|
28
36
|
}
|
|
29
37
|
super.close();
|
|
30
|
-
this.
|
|
38
|
+
for (const cb of this.closeCallbacks) {
|
|
39
|
+
cb();
|
|
40
|
+
}
|
|
31
41
|
}
|
|
32
42
|
registerOnSend(send) {
|
|
33
43
|
this.sendCallback = send;
|
|
@@ -38,4 +48,13 @@ export class PikkuLocalChannelHandler extends PikkuAbstractChannelHandler {
|
|
|
38
48
|
}
|
|
39
49
|
return this.sendCallback?.(message, isBinary);
|
|
40
50
|
}
|
|
51
|
+
registerOnSendBinary(send) {
|
|
52
|
+
this.sendBinaryCallback = send;
|
|
53
|
+
}
|
|
54
|
+
sendBinary(data) {
|
|
55
|
+
if (!this.sendBinaryCallback) {
|
|
56
|
+
throw new Error('No sendBinary callback registered');
|
|
57
|
+
}
|
|
58
|
+
this.sendBinaryCallback(data);
|
|
59
|
+
}
|
|
41
60
|
}
|
|
@@ -75,7 +75,10 @@ export const runLocalChannel = async ({ channelId, request, response, route, ski
|
|
|
75
75
|
}
|
|
76
76
|
catch (e) {
|
|
77
77
|
singletonServices.logger.error(`Error handling onConnect: ${e}`);
|
|
78
|
-
channel.send({
|
|
78
|
+
channel.send({
|
|
79
|
+
error: e.message || 'Unknown error',
|
|
80
|
+
errorName: e.constructor?.name,
|
|
81
|
+
});
|
|
79
82
|
}
|
|
80
83
|
}
|
|
81
84
|
});
|
|
@@ -94,22 +97,42 @@ export const runLocalChannel = async ({ channelId, request, response, route, ski
|
|
|
94
97
|
}
|
|
95
98
|
catch (e) {
|
|
96
99
|
singletonServices.logger.error(`Error handling onDisconnect: ${e}`);
|
|
97
|
-
channel.send({
|
|
100
|
+
channel.send({
|
|
101
|
+
error: e.message || 'Unknown error',
|
|
102
|
+
errorName: e.constructor?.name,
|
|
103
|
+
});
|
|
98
104
|
}
|
|
99
105
|
}
|
|
100
106
|
await closeServices();
|
|
101
107
|
});
|
|
102
|
-
const onMessage = processMessageHandlers(services, channelConfig, channelHandler, userSession);
|
|
108
|
+
const { onMessage, onBinaryMessage } = processMessageHandlers(services, channelConfig, channelHandler, userSession);
|
|
103
109
|
channelHandler.registerOnMessage(async (data) => {
|
|
104
110
|
try {
|
|
105
111
|
const result = await onMessage(data);
|
|
106
112
|
await channel.send(result);
|
|
107
113
|
}
|
|
108
114
|
catch (e) {
|
|
109
|
-
singletonServices.logger.error(
|
|
110
|
-
channel.send({
|
|
115
|
+
singletonServices.logger.error(e);
|
|
116
|
+
channel.send({
|
|
117
|
+
error: e.message || 'Unknown error',
|
|
118
|
+
errorName: e.constructor?.name,
|
|
119
|
+
});
|
|
120
|
+
setTimeout(() => channel.close(), 200);
|
|
111
121
|
}
|
|
112
122
|
});
|
|
123
|
+
if (onBinaryMessage) {
|
|
124
|
+
channelHandler.registerOnBinaryMessage(async (data) => {
|
|
125
|
+
try {
|
|
126
|
+
const result = await onBinaryMessage(data);
|
|
127
|
+
if (result) {
|
|
128
|
+
await channel.sendBinary(result);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (e) {
|
|
132
|
+
singletonServices.logger.error(`Error handling binary message: ${e.message}`);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
113
136
|
}
|
|
114
137
|
catch (e) {
|
|
115
138
|
handleHTTPError(e, http, channelId, singletonServices.logger, logWarningsForStatusCodes, respondWith404, bubbleErrors);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PikkuChannel, PikkuChannelHandler } from './channel.types.js';
|
|
1
|
+
import type { BinaryData, PikkuChannel, PikkuChannelHandler } from './channel.types.js';
|
|
2
2
|
export declare abstract class PikkuAbstractChannelHandler<OpeningData = unknown, Out = unknown> implements PikkuChannelHandler<OpeningData, Out> {
|
|
3
3
|
channelId: string;
|
|
4
4
|
channelName: string;
|
|
@@ -6,6 +6,7 @@ export declare abstract class PikkuAbstractChannelHandler<OpeningData = unknown,
|
|
|
6
6
|
protected channel?: PikkuChannel<OpeningData, Out>;
|
|
7
7
|
constructor(channelId: string, channelName: string, openingData: OpeningData);
|
|
8
8
|
abstract send(message: Out, isBinary?: boolean): Promise<void> | void;
|
|
9
|
+
abstract sendBinary(data: BinaryData): Promise<void> | void;
|
|
9
10
|
getChannel(): PikkuChannel<OpeningData, Out>;
|
|
10
11
|
open(): void;
|
|
11
12
|
close(): Promise<void> | void;
|
|
@@ -171,12 +171,15 @@ export const runChannelMessage = async ({ channelId, channelStore, channelHandle
|
|
|
171
171
|
};
|
|
172
172
|
let response;
|
|
173
173
|
try {
|
|
174
|
-
const onMessage = processMessageHandlers(services, channelConfig, channelHandler, userSession);
|
|
174
|
+
const { onMessage } = processMessageHandlers(services, channelConfig, channelHandler, userSession);
|
|
175
175
|
response = await onMessage(data);
|
|
176
176
|
}
|
|
177
177
|
catch (e) {
|
|
178
178
|
singletonServices.logger.error(`Error processing message: ${e.message || e}`);
|
|
179
|
-
return {
|
|
179
|
+
return {
|
|
180
|
+
error: e.message || 'Unknown error',
|
|
181
|
+
errorName: e.constructor?.name,
|
|
182
|
+
};
|
|
180
183
|
}
|
|
181
184
|
finally {
|
|
182
185
|
if (wireServices) {
|
|
@@ -130,14 +130,20 @@ function pluckCLIData(mergedData, funcName, availableOptions) {
|
|
|
130
130
|
const funcMeta = pikkuState(null, 'function', 'meta')[funcName];
|
|
131
131
|
const schemaName = funcMeta?.inputSchemaName;
|
|
132
132
|
const schema = schemaName
|
|
133
|
-
? pikkuState(null, 'misc', 'schemas').get(schemaName)
|
|
133
|
+
? pikkuState(funcMeta?.packageName ?? null, 'misc', 'schemas').get(schemaName)
|
|
134
134
|
: null;
|
|
135
135
|
if (schema && schema.properties) {
|
|
136
136
|
// If we have a schema, only include fields that are in the schema
|
|
137
137
|
const result = {};
|
|
138
138
|
for (const key of Object.keys(schema.properties)) {
|
|
139
139
|
if (key in mergedData) {
|
|
140
|
-
|
|
140
|
+
let value = mergedData[key];
|
|
141
|
+
// CLI passes all values as strings — coerce to array when schema expects one
|
|
142
|
+
const propSchema = schema.properties[key];
|
|
143
|
+
if (propSchema?.type === 'array' && !Array.isArray(value)) {
|
|
144
|
+
value = typeof value === 'string' ? value.split(',') : [value];
|
|
145
|
+
}
|
|
146
|
+
result[key] = value;
|
|
141
147
|
}
|
|
142
148
|
else if (availableOptions[key]?.default !== undefined) {
|
|
143
149
|
// Apply default if not provided
|
|
@@ -207,6 +213,9 @@ export async function runCLICommand({ program, commandPath, data, singletonServi
|
|
|
207
213
|
await Promise.resolve(renderer(singletonServices, data, undefined));
|
|
208
214
|
}
|
|
209
215
|
},
|
|
216
|
+
sendBinary: () => {
|
|
217
|
+
throw new Error('Binary data is not supported on CLI channels');
|
|
218
|
+
},
|
|
210
219
|
close: () => {
|
|
211
220
|
if (channel) {
|
|
212
221
|
channel.state = 'closed';
|
|
@@ -234,9 +243,11 @@ export async function runCLICommand({ program, commandPath, data, singletonServi
|
|
|
234
243
|
wireMiddleware: allWireMiddleware,
|
|
235
244
|
inheritedPermissions: currentCommand.permissions,
|
|
236
245
|
wirePermissions: undefined,
|
|
246
|
+
coerceDataFromSchema: true,
|
|
237
247
|
tags: programData?.tags,
|
|
238
248
|
wire,
|
|
239
249
|
sessionService: userSession,
|
|
250
|
+
packageName: currentCommand.packageName,
|
|
240
251
|
});
|
|
241
252
|
// Apply renderer one final time with the final output (if renderer exists)
|
|
242
253
|
// Skip if result is undefined (void functions handle their own output)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { CoreGateway } from './gateway.types.js';
|
|
2
|
+
import type { CoreSingletonServices } from '../../types/core.types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Register a messaging gateway.
|
|
5
|
+
*
|
|
6
|
+
* `wireGateway` is a meta-wiring that composes existing primitives
|
|
7
|
+
* (`wireHTTP`, `wireChannel`) under the hood. It does not replace them;
|
|
8
|
+
* it layers on top.
|
|
9
|
+
*
|
|
10
|
+
* @param config - Gateway configuration (name, type, adapter, func, middleware, etc.)
|
|
11
|
+
*/
|
|
12
|
+
export declare const wireGateway: (config: CoreGateway) => void;
|
|
13
|
+
/**
|
|
14
|
+
* Create the message handler callback for a listener gateway.
|
|
15
|
+
*
|
|
16
|
+
* Returns a function `(rawData: unknown) => Promise<void>` that can be
|
|
17
|
+
* passed to `adapter.init()`. Used by `LocalGatewayService` (and any
|
|
18
|
+
* other GatewayService implementation) to wire up listener gateways.
|
|
19
|
+
*
|
|
20
|
+
* @param name - Gateway name (for wire.gateway metadata)
|
|
21
|
+
* @param config - The gateway configuration (adapter, func, middleware)
|
|
22
|
+
* @param singletonServices - Singleton services to pass to handler/middleware
|
|
23
|
+
*/
|
|
24
|
+
export declare const createListenerMessageHandler: (name: string, config: CoreGateway, singletonServices: CoreSingletonServices) => ((rawData: unknown) => Promise<void>);
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { pikkuState } from '../../pikku-state.js';
|
|
2
|
+
import { addFunction } from '../../function/function-runner.js';
|
|
3
|
+
import { runMiddleware } from '../../middleware-runner.js';
|
|
4
|
+
import { httpRouter } from '../http/routers/http-router.js';
|
|
5
|
+
/**
|
|
6
|
+
* Register a messaging gateway.
|
|
7
|
+
*
|
|
8
|
+
* `wireGateway` is a meta-wiring that composes existing primitives
|
|
9
|
+
* (`wireHTTP`, `wireChannel`) under the hood. It does not replace them;
|
|
10
|
+
* it layers on top.
|
|
11
|
+
*
|
|
12
|
+
* @param config - Gateway configuration (name, type, adapter, func, middleware, etc.)
|
|
13
|
+
*/
|
|
14
|
+
export const wireGateway = (config) => {
|
|
15
|
+
// Store gateway config
|
|
16
|
+
pikkuState(null, 'gateway', 'gateways').set(config.name, config);
|
|
17
|
+
switch (config.type) {
|
|
18
|
+
case 'webhook':
|
|
19
|
+
wireWebhookGateway(config);
|
|
20
|
+
break;
|
|
21
|
+
case 'websocket':
|
|
22
|
+
wireWebsocketGateway(config);
|
|
23
|
+
break;
|
|
24
|
+
case 'listener':
|
|
25
|
+
wireListenerGateway(config);
|
|
26
|
+
break;
|
|
27
|
+
default:
|
|
28
|
+
throw new Error(`Unknown gateway type '${config.type}' for gateway '${config.name}'`);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Webhook gateway — platform POSTs to us
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
const wireWebhookGateway = (config) => {
|
|
35
|
+
const { name, route, adapter } = config;
|
|
36
|
+
if (!route) {
|
|
37
|
+
throw new Error(`Webhook gateway '${name}' requires a route`);
|
|
38
|
+
}
|
|
39
|
+
const postFuncId = `gateway__${name}__post`;
|
|
40
|
+
const httpMeta = pikkuState(null, 'http', 'meta');
|
|
41
|
+
const funcMeta = pikkuState(null, 'function', 'meta');
|
|
42
|
+
const routes = pikkuState(null, 'http', 'routes');
|
|
43
|
+
// --- POST handler (main message receiver) --------------------------------
|
|
44
|
+
funcMeta[postFuncId] = {
|
|
45
|
+
pikkuFuncId: postFuncId,
|
|
46
|
+
inputSchemaName: null,
|
|
47
|
+
outputSchemaName: null,
|
|
48
|
+
sessionless: true,
|
|
49
|
+
};
|
|
50
|
+
httpMeta['post'][route] = {
|
|
51
|
+
pikkuFuncId: postFuncId,
|
|
52
|
+
route,
|
|
53
|
+
method: 'post',
|
|
54
|
+
};
|
|
55
|
+
const postHandler = {
|
|
56
|
+
auth: false,
|
|
57
|
+
func: createWebhookPostHandler(config),
|
|
58
|
+
};
|
|
59
|
+
addFunction(postFuncId, postHandler);
|
|
60
|
+
if (!routes.has('post')) {
|
|
61
|
+
routes.set('post', new Map());
|
|
62
|
+
}
|
|
63
|
+
routes.get('post').set(route, {
|
|
64
|
+
method: 'post',
|
|
65
|
+
route,
|
|
66
|
+
func: postHandler,
|
|
67
|
+
auth: false,
|
|
68
|
+
});
|
|
69
|
+
// --- GET handler (webhook verification, e.g. WhatsApp challenge) ---------
|
|
70
|
+
if (adapter.verifyWebhook) {
|
|
71
|
+
const verifyFuncId = `gateway__${name}__verify`;
|
|
72
|
+
funcMeta[verifyFuncId] = {
|
|
73
|
+
pikkuFuncId: verifyFuncId,
|
|
74
|
+
inputSchemaName: null,
|
|
75
|
+
outputSchemaName: null,
|
|
76
|
+
sessionless: true,
|
|
77
|
+
};
|
|
78
|
+
httpMeta['get'][route] = {
|
|
79
|
+
pikkuFuncId: verifyFuncId,
|
|
80
|
+
route,
|
|
81
|
+
method: 'get',
|
|
82
|
+
};
|
|
83
|
+
const verifyHandler = {
|
|
84
|
+
auth: false,
|
|
85
|
+
func: createWebhookVerifyHandler(adapter),
|
|
86
|
+
};
|
|
87
|
+
addFunction(verifyFuncId, verifyHandler);
|
|
88
|
+
if (!routes.has('get')) {
|
|
89
|
+
routes.set('get', new Map());
|
|
90
|
+
}
|
|
91
|
+
routes.get('get').set(route, {
|
|
92
|
+
method: 'get',
|
|
93
|
+
route,
|
|
94
|
+
func: verifyHandler,
|
|
95
|
+
auth: false,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
// Force router to re-initialize on next match
|
|
99
|
+
httpRouter.reset();
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Creates the POST handler function for a webhook gateway.
|
|
103
|
+
*
|
|
104
|
+
* Flow:
|
|
105
|
+
* 1. Check for POST-based verification (e.g. Slack `url_verification`)
|
|
106
|
+
* 2. Parse body via adapter → GatewayInboundMessage (or null to ignore)
|
|
107
|
+
* 3. Populate `wire.gateway`
|
|
108
|
+
* 4. Run user middleware (which can read `wire.gateway` for auth)
|
|
109
|
+
* 5. Call user func with parsed message
|
|
110
|
+
* 6. Auto-send response via adapter if func returns outbound content
|
|
111
|
+
*/
|
|
112
|
+
const createWebhookPostHandler = (config) => {
|
|
113
|
+
const { name, adapter, func: userFunc, middleware: userMiddleware } = config;
|
|
114
|
+
const userFuncConfig = userFunc;
|
|
115
|
+
return async (services, data, wire) => {
|
|
116
|
+
// Check for POST-based webhook verification (e.g. Slack url_verification)
|
|
117
|
+
if (adapter.verifyWebhook) {
|
|
118
|
+
const verifyResult = await adapter.verifyWebhook(data, wire.http?.request);
|
|
119
|
+
if (verifyResult.verified) {
|
|
120
|
+
return verifyResult.response;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Parse the platform-specific payload
|
|
124
|
+
const parsed = adapter.parse(data);
|
|
125
|
+
if (!parsed) {
|
|
126
|
+
// Ignored event (delivery receipt, typing indicator, etc.)
|
|
127
|
+
return { ok: true };
|
|
128
|
+
}
|
|
129
|
+
// Populate wire.gateway
|
|
130
|
+
const gateway = {
|
|
131
|
+
gatewayName: name,
|
|
132
|
+
senderId: parsed.senderId,
|
|
133
|
+
platform: adapter.name,
|
|
134
|
+
send: (msg) => adapter.send(parsed.senderId, msg),
|
|
135
|
+
};
|
|
136
|
+
wire.gateway = gateway;
|
|
137
|
+
// Build combined middleware chain: gateway-level + func-level
|
|
138
|
+
const allMiddleware = [
|
|
139
|
+
...(userMiddleware || []),
|
|
140
|
+
...(userFuncConfig.middleware || []),
|
|
141
|
+
];
|
|
142
|
+
const exec = async () => {
|
|
143
|
+
const result = await userFuncConfig.func(services, parsed, wire);
|
|
144
|
+
// Auto-send response if the func returns outbound content
|
|
145
|
+
if (result && (result.text || result.richContent || result.attachments)) {
|
|
146
|
+
await adapter.send(parsed.senderId, result);
|
|
147
|
+
}
|
|
148
|
+
return { ok: true };
|
|
149
|
+
};
|
|
150
|
+
if (allMiddleware.length > 0) {
|
|
151
|
+
return await runMiddleware(services, wire, allMiddleware, exec);
|
|
152
|
+
}
|
|
153
|
+
return await exec();
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* Creates the GET handler for webhook verification challenges.
|
|
158
|
+
* Passes query parameters to the adapter's verifyWebhook method.
|
|
159
|
+
*/
|
|
160
|
+
const createWebhookVerifyHandler = (adapter) => {
|
|
161
|
+
return async (_services, _data, wire) => {
|
|
162
|
+
if (!adapter.verifyWebhook) {
|
|
163
|
+
return { error: 'Verification not supported' };
|
|
164
|
+
}
|
|
165
|
+
const query = wire.http?.request?.query();
|
|
166
|
+
const result = await adapter.verifyWebhook(query, wire.http?.request);
|
|
167
|
+
if (result.verified) {
|
|
168
|
+
return result.response;
|
|
169
|
+
}
|
|
170
|
+
return { error: 'Verification failed' };
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
// WebSocket gateway — client connects via WebSocket
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
const wireWebsocketGateway = (config) => {
|
|
177
|
+
const { name, route, adapter } = config;
|
|
178
|
+
if (!route) {
|
|
179
|
+
throw new Error(`WebSocket gateway '${name}' requires a route`);
|
|
180
|
+
}
|
|
181
|
+
// Store gateway config — the channel runner will reference this
|
|
182
|
+
pikkuState(null, 'gateway', 'gateways').set(config.name, config);
|
|
183
|
+
const channelsMeta = pikkuState(null, 'channel', 'meta');
|
|
184
|
+
const channels = pikkuState(null, 'channel', 'channels');
|
|
185
|
+
const messageFuncId = `gateway__${name}__message`;
|
|
186
|
+
const connectFuncId = `gateway__${name}__connect`;
|
|
187
|
+
// Add function metadata
|
|
188
|
+
const funcMeta = pikkuState(null, 'function', 'meta');
|
|
189
|
+
funcMeta[messageFuncId] = {
|
|
190
|
+
pikkuFuncId: messageFuncId,
|
|
191
|
+
inputSchemaName: null,
|
|
192
|
+
outputSchemaName: null,
|
|
193
|
+
sessionless: true,
|
|
194
|
+
};
|
|
195
|
+
funcMeta[connectFuncId] = {
|
|
196
|
+
pikkuFuncId: connectFuncId,
|
|
197
|
+
inputSchemaName: null,
|
|
198
|
+
outputSchemaName: null,
|
|
199
|
+
sessionless: true,
|
|
200
|
+
};
|
|
201
|
+
// Add channel metadata
|
|
202
|
+
channelsMeta[name] = {
|
|
203
|
+
name,
|
|
204
|
+
route,
|
|
205
|
+
gateway: true,
|
|
206
|
+
connect: { pikkuFuncId: connectFuncId },
|
|
207
|
+
message: { pikkuFuncId: messageFuncId },
|
|
208
|
+
};
|
|
209
|
+
const userFuncConfig = config.func;
|
|
210
|
+
const userMiddleware = config.middleware;
|
|
211
|
+
// Register onConnect
|
|
212
|
+
addFunction(connectFuncId, {
|
|
213
|
+
auth: false,
|
|
214
|
+
func: async (_services, _data, wire) => {
|
|
215
|
+
;
|
|
216
|
+
wire.gateway = {
|
|
217
|
+
gatewayName: name,
|
|
218
|
+
senderId: '',
|
|
219
|
+
platform: adapter.name,
|
|
220
|
+
send: async (msg) => {
|
|
221
|
+
wire.channel?.send(msg);
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
// Register onMessage
|
|
227
|
+
addFunction(messageFuncId, {
|
|
228
|
+
auth: false,
|
|
229
|
+
func: async (services, data, wire) => {
|
|
230
|
+
const parsed = adapter.parse(data);
|
|
231
|
+
if (!parsed)
|
|
232
|
+
return;
|
|
233
|
+
const gateway = {
|
|
234
|
+
gatewayName: name,
|
|
235
|
+
senderId: parsed.senderId,
|
|
236
|
+
platform: adapter.name,
|
|
237
|
+
send: async (msg) => {
|
|
238
|
+
wire.channel?.send(msg);
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
wire.gateway = gateway;
|
|
242
|
+
const allMiddleware = [
|
|
243
|
+
...(userMiddleware || []),
|
|
244
|
+
...(userFuncConfig.middleware || []),
|
|
245
|
+
];
|
|
246
|
+
const exec = async () => {
|
|
247
|
+
const result = await userFuncConfig.func(services, parsed, wire);
|
|
248
|
+
if (result &&
|
|
249
|
+
(result.text || result.richContent || result.attachments)) {
|
|
250
|
+
wire.channel?.send(result);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
if (allMiddleware.length > 0) {
|
|
254
|
+
await runMiddleware(services, wire, allMiddleware, exec);
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
await exec();
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
// Store channel config
|
|
262
|
+
channels.set(name, {
|
|
263
|
+
name,
|
|
264
|
+
route,
|
|
265
|
+
auth: false,
|
|
266
|
+
onConnect: { func: async () => { } },
|
|
267
|
+
onMessage: { func: async () => { } },
|
|
268
|
+
});
|
|
269
|
+
// Force router to re-initialize
|
|
270
|
+
httpRouter.reset();
|
|
271
|
+
};
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
// Listener gateway — standalone event loop, no route
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
const wireListenerGateway = (config) => {
|
|
276
|
+
// Listener gateways don't register routes.
|
|
277
|
+
// They are started manually and call into the gateway's func directly.
|
|
278
|
+
// The gateway config is already stored in pikkuState.
|
|
279
|
+
pikkuState(null, 'gateway', 'gateways').set(config.name, config);
|
|
280
|
+
};
|
|
281
|
+
/**
|
|
282
|
+
* Create the message handler callback for a listener gateway.
|
|
283
|
+
*
|
|
284
|
+
* Returns a function `(rawData: unknown) => Promise<void>` that can be
|
|
285
|
+
* passed to `adapter.init()`. Used by `LocalGatewayService` (and any
|
|
286
|
+
* other GatewayService implementation) to wire up listener gateways.
|
|
287
|
+
*
|
|
288
|
+
* @param name - Gateway name (for wire.gateway metadata)
|
|
289
|
+
* @param config - The gateway configuration (adapter, func, middleware)
|
|
290
|
+
* @param singletonServices - Singleton services to pass to handler/middleware
|
|
291
|
+
*/
|
|
292
|
+
export const createListenerMessageHandler = (name, config, singletonServices) => {
|
|
293
|
+
const { adapter } = config;
|
|
294
|
+
const userFuncConfig = config.func;
|
|
295
|
+
const userMiddleware = config.middleware;
|
|
296
|
+
return async (rawData) => {
|
|
297
|
+
const parsed = adapter.parse(rawData);
|
|
298
|
+
if (!parsed)
|
|
299
|
+
return;
|
|
300
|
+
const wire = {};
|
|
301
|
+
const gateway = {
|
|
302
|
+
gatewayName: name,
|
|
303
|
+
senderId: parsed.senderId,
|
|
304
|
+
platform: adapter.name,
|
|
305
|
+
send: (msg) => adapter.send(parsed.senderId, msg),
|
|
306
|
+
};
|
|
307
|
+
wire.gateway = gateway;
|
|
308
|
+
const allMiddleware = [
|
|
309
|
+
...(userMiddleware || []),
|
|
310
|
+
...(userFuncConfig.middleware || []),
|
|
311
|
+
];
|
|
312
|
+
const exec = async () => {
|
|
313
|
+
const result = await userFuncConfig.func(singletonServices, parsed, wire);
|
|
314
|
+
if (result && (result.text || result.richContent || result.attachments)) {
|
|
315
|
+
await adapter.send(parsed.senderId, result);
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
if (allMiddleware.length > 0) {
|
|
319
|
+
await runMiddleware(singletonServices, wire, allMiddleware, exec);
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
await exec();
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
};
|