@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.
Files changed (165) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/crypto-utils.d.ts +10 -0
  3. package/dist/crypto-utils.js +62 -0
  4. package/dist/function/function-runner.js +4 -4
  5. package/dist/function/functions.types.d.ts +26 -1
  6. package/dist/function/functions.types.js +16 -0
  7. package/dist/handle-error.d.ts +1 -1
  8. package/dist/handle-error.js +7 -3
  9. package/dist/index.d.ts +14 -2
  10. package/dist/index.js +1 -1
  11. package/dist/middleware-runner.js +1 -0
  12. package/dist/permissions.js +1 -0
  13. package/dist/pikku-state.js +7 -1
  14. package/dist/schema.js +1 -1
  15. package/dist/services/gateway-service.d.ts +19 -0
  16. package/dist/services/gateway-service.js +1 -0
  17. package/dist/services/in-memory-workflow-service.d.ts +8 -2
  18. package/dist/services/in-memory-workflow-service.js +28 -1
  19. package/dist/services/index.d.ts +2 -0
  20. package/dist/services/index.js +1 -0
  21. package/dist/services/local-gateway-service.d.ts +22 -0
  22. package/dist/services/local-gateway-service.js +51 -0
  23. package/dist/services/workflow-service.d.ts +8 -2
  24. package/dist/testing/index.d.ts +2 -0
  25. package/dist/testing/index.js +1 -0
  26. package/dist/testing/service-tests.d.ts +31 -0
  27. package/dist/testing/service-tests.js +598 -0
  28. package/dist/types/core.types.d.ts +12 -2
  29. package/dist/types/state.types.d.ts +9 -2
  30. package/dist/utils/hash.d.ts +2 -0
  31. package/dist/utils/hash.js +23 -0
  32. package/dist/utils.js +1 -1
  33. package/dist/wirings/ai-agent/agent-dynamic-workflow.d.ts +6 -0
  34. package/dist/wirings/ai-agent/agent-dynamic-workflow.js +348 -0
  35. package/dist/wirings/ai-agent/ai-agent-memory.d.ts +1 -1
  36. package/dist/wirings/ai-agent/ai-agent-memory.js +19 -7
  37. package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +10 -7
  38. package/dist/wirings/ai-agent/ai-agent-prepare.js +141 -48
  39. package/dist/wirings/ai-agent/ai-agent-runner.d.ts +4 -0
  40. package/dist/wirings/ai-agent/ai-agent-runner.js +381 -27
  41. package/dist/wirings/ai-agent/ai-agent-stream.d.ts +7 -2
  42. package/dist/wirings/ai-agent/ai-agent-stream.js +126 -48
  43. package/dist/wirings/ai-agent/ai-agent.types.d.ts +57 -3
  44. package/dist/wirings/ai-agent/index.d.ts +2 -3
  45. package/dist/wirings/ai-agent/index.js +1 -2
  46. package/dist/wirings/channel/channel-handler.d.ts +5 -2
  47. package/dist/wirings/channel/channel-handler.js +16 -2
  48. package/dist/wirings/channel/channel-middleware-runner.js +1 -0
  49. package/dist/wirings/channel/channel.types.d.ts +7 -7
  50. package/dist/wirings/channel/index.d.ts +1 -1
  51. package/dist/wirings/channel/local/local-channel-handler.d.ts +8 -1
  52. package/dist/wirings/channel/local/local-channel-handler.js +22 -3
  53. package/dist/wirings/channel/local/local-channel-runner.js +28 -5
  54. package/dist/wirings/channel/pikku-abstract-channel-handler.d.ts +2 -1
  55. package/dist/wirings/channel/pikku-abstract-channel-handler.js +1 -0
  56. package/dist/wirings/channel/serverless/serverless-channel-runner.js +5 -2
  57. package/dist/wirings/cli/cli-runner.js +13 -2
  58. package/dist/wirings/cli/cli.types.d.ts +1 -0
  59. package/dist/wirings/gateway/gateway-runner.d.ts +24 -0
  60. package/dist/wirings/gateway/gateway-runner.js +325 -0
  61. package/dist/wirings/gateway/gateway.types.d.ts +127 -0
  62. package/dist/wirings/gateway/gateway.types.js +1 -0
  63. package/dist/wirings/gateway/index.d.ts +2 -0
  64. package/dist/wirings/gateway/index.js +1 -0
  65. package/dist/wirings/http/http-routes.js +0 -1
  66. package/dist/wirings/http/http-runner.d.ts +1 -1
  67. package/dist/wirings/http/http-runner.js +28 -3
  68. package/dist/wirings/http/http.types.d.ts +3 -11
  69. package/dist/wirings/http/index.d.ts +2 -1
  70. package/dist/wirings/http/index.js +1 -0
  71. package/dist/wirings/http/pikku-fetch-http-request.d.ts +1 -0
  72. package/dist/wirings/http/pikku-fetch-http-request.js +3 -0
  73. package/dist/wirings/http/pikku-fetch-http-response.d.ts +1 -0
  74. package/dist/wirings/http/pikku-fetch-http-response.js +4 -4
  75. package/dist/wirings/http/web-request.d.ts +12 -0
  76. package/dist/wirings/http/web-request.js +85 -0
  77. package/dist/wirings/mcp/mcp-endpoint-registry.js +1 -1
  78. package/dist/wirings/mcp/mcp-runner.js +13 -1
  79. package/dist/wirings/rpc/rpc-runner.d.ts +30 -6
  80. package/dist/wirings/rpc/rpc-runner.js +36 -14
  81. package/dist/wirings/rpc/rpc-types.d.ts +6 -1
  82. package/dist/wirings/rpc/wire-addon.d.ts +1 -0
  83. package/dist/wirings/rpc/wire-addon.js +1 -1
  84. package/dist/wirings/scheduler/scheduler-runner.js +1 -0
  85. package/dist/wirings/workflow/graph/graph-validation.d.ts +3 -0
  86. package/dist/wirings/workflow/graph/graph-validation.js +175 -0
  87. package/dist/wirings/workflow/graph/index.d.ts +1 -0
  88. package/dist/wirings/workflow/graph/index.js +1 -0
  89. package/dist/wirings/workflow/index.d.ts +1 -1
  90. package/dist/wirings/workflow/pikku-workflow-service.d.ts +8 -2
  91. package/dist/wirings/workflow/pikku-workflow-service.js +1 -2
  92. package/dist/wirings/workflow/workflow.types.d.ts +10 -1
  93. package/package.json +6 -3
  94. package/src/crypto-utils.test.ts +99 -1
  95. package/src/crypto-utils.ts +114 -0
  96. package/src/function/function-runner.ts +5 -5
  97. package/src/function/functions.types.ts +38 -1
  98. package/src/handle-error.test.ts +65 -2
  99. package/src/handle-error.ts +8 -4
  100. package/src/index.ts +14 -0
  101. package/src/middleware-runner.ts +1 -0
  102. package/src/permissions.ts +1 -0
  103. package/src/pikku-state.ts +8 -1
  104. package/src/schema.ts +1 -1
  105. package/src/services/gateway-service.ts +20 -0
  106. package/src/services/in-memory-workflow-service.ts +45 -2
  107. package/src/services/index.ts +2 -0
  108. package/src/services/local-gateway-service.ts +62 -0
  109. package/src/services/workflow-service.ts +12 -1
  110. package/src/testing/index.ts +2 -0
  111. package/src/testing/service-tests.ts +873 -0
  112. package/src/types/core.types.ts +12 -1
  113. package/src/types/state.types.ts +12 -2
  114. package/src/utils/hash.test.ts +68 -0
  115. package/src/utils/hash.ts +26 -0
  116. package/src/utils.ts +1 -1
  117. package/src/wirings/ai-agent/agent-dynamic-workflow.ts +469 -0
  118. package/src/wirings/ai-agent/ai-agent-memory.ts +17 -7
  119. package/src/wirings/ai-agent/ai-agent-prepare.ts +194 -62
  120. package/src/wirings/ai-agent/ai-agent-runner.test.ts +3 -1
  121. package/src/wirings/ai-agent/ai-agent-runner.ts +553 -31
  122. package/src/wirings/ai-agent/ai-agent-stream.test.ts +209 -2
  123. package/src/wirings/ai-agent/ai-agent-stream.ts +195 -91
  124. package/src/wirings/ai-agent/ai-agent.types.ts +57 -3
  125. package/src/wirings/ai-agent/index.ts +3 -5
  126. package/src/wirings/channel/channel-handler.ts +41 -4
  127. package/src/wirings/channel/channel-middleware-runner.ts +1 -0
  128. package/src/wirings/channel/channel.types.ts +12 -7
  129. package/src/wirings/channel/index.ts +1 -0
  130. package/src/wirings/channel/local/local-channel-handler.ts +31 -3
  131. package/src/wirings/channel/local/local-channel-runner.ts +32 -8
  132. package/src/wirings/channel/pikku-abstract-channel-handler.test.ts +2 -0
  133. package/src/wirings/channel/pikku-abstract-channel-handler.ts +8 -1
  134. package/src/wirings/channel/serverless/serverless-channel-runner.ts +5 -2
  135. package/src/wirings/cli/cli-runner.ts +15 -2
  136. package/src/wirings/cli/cli.types.ts +1 -0
  137. package/src/wirings/gateway/gateway-runner.test.ts +474 -0
  138. package/src/wirings/gateway/gateway-runner.ts +411 -0
  139. package/src/wirings/gateway/gateway.types.ts +149 -0
  140. package/src/wirings/gateway/index.ts +13 -0
  141. package/src/wirings/http/http-routes.ts +0 -1
  142. package/src/wirings/http/http-runner.ts +38 -10
  143. package/src/wirings/http/http.types.ts +3 -12
  144. package/src/wirings/http/index.ts +3 -0
  145. package/src/wirings/http/pikku-fetch-http-request.ts +4 -0
  146. package/src/wirings/http/pikku-fetch-http-response.ts +4 -4
  147. package/src/wirings/http/web-request.test.ts +236 -0
  148. package/src/wirings/http/web-request.ts +104 -0
  149. package/src/wirings/mcp/mcp-endpoint-registry.ts +1 -1
  150. package/src/wirings/mcp/mcp-runner.ts +30 -12
  151. package/src/wirings/rpc/rpc-runner.ts +45 -24
  152. package/src/wirings/rpc/rpc-types.ts +6 -1
  153. package/src/wirings/rpc/wire-addon.ts +2 -1
  154. package/src/wirings/scheduler/scheduler-runner.ts +1 -0
  155. package/src/wirings/workflow/graph/graph-validation.test.ts +170 -0
  156. package/src/wirings/workflow/graph/graph-validation.ts +237 -0
  157. package/src/wirings/workflow/graph/index.ts +5 -0
  158. package/src/wirings/workflow/index.ts +1 -0
  159. package/src/wirings/workflow/pikku-workflow-service.ts +14 -3
  160. package/src/wirings/workflow/workflow.types.ts +9 -1
  161. package/tsconfig.tsbuildinfo +1 -1
  162. package/dist/wirings/ai-agent/ai-agent-assistant-ui.d.ts +0 -5
  163. package/dist/wirings/ai-agent/ai-agent-assistant-ui.js +0 -146
  164. package/src/wirings/ai-agent/ai-agent-assistant-ui.test.ts +0 -363
  165. 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 closeCallback?;
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
- closeCallback;
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.closeCallback = callback;
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.closeCallback?.();
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({ error: e.message || 'Unknown error' });
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({ error: e.message || 'Unknown error' });
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(`Error handling message: ${e.message}`);
110
- channel.send({ error: e.message || 'Unknown error' });
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;
@@ -14,6 +14,7 @@ export class PikkuAbstractChannelHandler {
14
14
  channelId: this.channelId,
15
15
  openingData: this.openingData,
16
16
  send: this.send.bind(this),
17
+ sendBinary: this.sendBinary.bind(this),
17
18
  close: this.close.bind(this),
18
19
  state: 'initial',
19
20
  };
@@ -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 { error: e.message || 'Unknown error' };
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
- result[key] = mergedData[key];
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)
@@ -52,6 +52,7 @@ export interface CLIProgramState {
52
52
  export interface CLICommandMeta {
53
53
  parameters?: string;
54
54
  pikkuFuncId: string;
55
+ packageName?: string;
55
56
  positionals: CLIPositional[];
56
57
  options: Record<string, CLIOption>;
57
58
  renderName?: string;
@@ -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
+ };