@pikku/core 0.12.19 → 0.12.21
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 +77 -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/function-runner.js +53 -3
- package/dist/function/functions.types.d.ts +3 -2
- package/dist/index.d.ts +3 -3
- 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/content-service.d.ts +66 -37
- 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/index.d.ts +1 -1
- package/dist/services/local-content.d.ts +10 -12
- package/dist/services/local-content.js +54 -38
- 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.d.ts +3 -2
- package/dist/wirings/rpc/rpc-runner.js +45 -11
- 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/function-runner.ts +68 -3
- package/src/function/functions.types.ts +3 -2
- package/src/index.ts +22 -3
- 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/content-service.ts +79 -51
- 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/index.ts +10 -1
- package/src/services/local-content.test.ts +54 -0
- package/src/services/local-content.ts +80 -53
- 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 +60 -21
- 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
|
@@ -239,8 +239,10 @@ const executeRoute = async (services, matchedRoute, http, options) => {
|
|
|
239
239
|
response.setMode('stream');
|
|
240
240
|
response.header('Content-Type', 'text/event-stream');
|
|
241
241
|
response.header('Cache-Control', 'no-cache');
|
|
242
|
+
let sseState;
|
|
243
|
+
const channelId = createWeakUID();
|
|
242
244
|
channel = {
|
|
243
|
-
channelId
|
|
245
|
+
channelId,
|
|
244
246
|
openingData: await data(),
|
|
245
247
|
send: (data) => {
|
|
246
248
|
response.arrayBuffer(isSerializable(data) ? JSON.stringify(data) : data);
|
|
@@ -253,7 +255,36 @@ const executeRoute = async (services, matchedRoute, http, options) => {
|
|
|
253
255
|
response.close?.();
|
|
254
256
|
},
|
|
255
257
|
state: 'open',
|
|
258
|
+
setState: (s) => {
|
|
259
|
+
sseState = s;
|
|
260
|
+
},
|
|
261
|
+
getState: () => sseState,
|
|
262
|
+
clearState: () => {
|
|
263
|
+
sseState = undefined;
|
|
264
|
+
},
|
|
256
265
|
};
|
|
266
|
+
// Register the SSE channel with the eventHub (if present) so that
|
|
267
|
+
// eventHub.publish() can deliver messages to SSE subscribers.
|
|
268
|
+
// The channel handler wraps the SSE channel to match PikkuChannelHandler.
|
|
269
|
+
if (singletonServices.eventHub?.onChannelOpened) {
|
|
270
|
+
const channelRef = channel;
|
|
271
|
+
const channelHandler = {
|
|
272
|
+
getChannel: () => channelRef,
|
|
273
|
+
send: (data, isBinary) => {
|
|
274
|
+
if (isBinary)
|
|
275
|
+
channelRef.sendBinary(data);
|
|
276
|
+
else
|
|
277
|
+
channelRef.send(data);
|
|
278
|
+
},
|
|
279
|
+
sendBinary: (data) => channelRef.sendBinary(data),
|
|
280
|
+
};
|
|
281
|
+
singletonServices.eventHub.onChannelOpened(channelHandler);
|
|
282
|
+
const originalClose = channel.close;
|
|
283
|
+
channel.close = () => {
|
|
284
|
+
singletonServices.eventHub.onChannelClosed(channelId);
|
|
285
|
+
originalClose();
|
|
286
|
+
};
|
|
287
|
+
}
|
|
257
288
|
}
|
|
258
289
|
const wire = {
|
|
259
290
|
traceId: requestId,
|
|
@@ -264,22 +295,45 @@ const executeRoute = async (services, matchedRoute, http, options) => {
|
|
|
264
295
|
getSession: () => userSession.get(),
|
|
265
296
|
hasSessionChanged: () => userSession.sessionChanged,
|
|
266
297
|
};
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
298
|
+
try {
|
|
299
|
+
result = await runPikkuFunc('http', `${meta.method}:${meta.route}`, meta.pikkuFuncId, {
|
|
300
|
+
singletonServices,
|
|
301
|
+
createWireServices,
|
|
302
|
+
auth: route.auth !== false,
|
|
303
|
+
data,
|
|
304
|
+
inheritedMiddleware: meta.middleware,
|
|
305
|
+
wireMiddleware: route.middleware,
|
|
306
|
+
inheritedPermissions: meta.permissions,
|
|
307
|
+
wirePermissions: route.permissions,
|
|
308
|
+
coerceDataFromSchema: options.coerceDataFromSchema,
|
|
309
|
+
tags: route.tags,
|
|
310
|
+
wire,
|
|
311
|
+
sessionService: userSession,
|
|
312
|
+
packageName: meta.packageName,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
catch (e) {
|
|
316
|
+
if (matchedRoute.route.sse) {
|
|
317
|
+
singletonServices.logger.error(e instanceof Error ? e.message : e);
|
|
318
|
+
try {
|
|
319
|
+
const errorResponse = getErrorResponse(e);
|
|
320
|
+
http?.response?.arrayBuffer(JSON.stringify({
|
|
321
|
+
type: 'error',
|
|
322
|
+
errorText: errorResponse?.message ?? 'Internal server error',
|
|
323
|
+
}));
|
|
324
|
+
http?.response?.arrayBuffer(JSON.stringify({ type: 'done' }));
|
|
325
|
+
}
|
|
326
|
+
catch { }
|
|
327
|
+
channel?.close();
|
|
328
|
+
return wireServices ? { result, wireServices } : { result };
|
|
329
|
+
}
|
|
330
|
+
throw e;
|
|
331
|
+
}
|
|
332
|
+
if (matchedRoute.route.sse) {
|
|
333
|
+
// Flush headers after middleware has run so CORS/auth headers are included
|
|
334
|
+
http?.response?.flushHeaders?.();
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
283
337
|
if (result instanceof Response) {
|
|
284
338
|
await applyWebResponse(http.response, result);
|
|
285
339
|
}
|
|
@@ -405,25 +459,7 @@ export const fetchData = async (request, response, { skipUserSession = false, re
|
|
|
405
459
|
return result;
|
|
406
460
|
}
|
|
407
461
|
catch (e) {
|
|
408
|
-
|
|
409
|
-
// For SSE routes, send error through the stream since the response is already in stream mode
|
|
410
|
-
scopedLogger.error(e instanceof Error ? e.message : e);
|
|
411
|
-
try {
|
|
412
|
-
const errorResponse = getErrorResponse(e);
|
|
413
|
-
response.arrayBuffer(JSON.stringify({
|
|
414
|
-
type: 'error',
|
|
415
|
-
errorText: errorResponse?.message ?? 'Internal server error',
|
|
416
|
-
}));
|
|
417
|
-
response.arrayBuffer(JSON.stringify({ type: 'done' }));
|
|
418
|
-
}
|
|
419
|
-
catch (streamErr) {
|
|
420
|
-
scopedLogger.error(`SSE error while sending error payload: ${streamErr instanceof Error ? streamErr.message : String(streamErr)}`);
|
|
421
|
-
}
|
|
422
|
-
response.close?.();
|
|
423
|
-
}
|
|
424
|
-
else {
|
|
425
|
-
handleHTTPError(e, http, requestId, scopedLogger, logWarningsForStatusCodes, respondWith404, bubbleErrors, exposeErrors);
|
|
426
|
-
}
|
|
462
|
+
handleHTTPError(e, http, requestId, scopedLogger, logWarningsForStatusCodes, respondWith404, bubbleErrors, exposeErrors);
|
|
427
463
|
}
|
|
428
464
|
finally {
|
|
429
465
|
// Clean up any session-specific services created during processing
|
|
@@ -177,6 +177,7 @@ export interface PikkuHTTPResponse<Out = unknown> {
|
|
|
177
177
|
redirect(location: string, status?: number): this;
|
|
178
178
|
close?: () => void;
|
|
179
179
|
setMode?: (mode: 'stream') => void;
|
|
180
|
+
flushHeaders?: () => void;
|
|
180
181
|
}
|
|
181
182
|
/**
|
|
182
183
|
* Single route configuration - supports all wireHTTP options
|
|
@@ -89,7 +89,9 @@ export class PikkuFetchHTTPRequest {
|
|
|
89
89
|
const merged = {};
|
|
90
90
|
for (const part of parts) {
|
|
91
91
|
for (const [key, value] of Object.entries(part)) {
|
|
92
|
-
if (key === '__proto__' ||
|
|
92
|
+
if (key === '__proto__' ||
|
|
93
|
+
key === 'constructor' ||
|
|
94
|
+
key === 'prototype') {
|
|
93
95
|
continue;
|
|
94
96
|
}
|
|
95
97
|
if (key in merged && !valuesAreEquivalent(merged[key], value)) {
|
|
@@ -57,17 +57,46 @@ export function toWebRequest(req, baseUrl) {
|
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
59
|
const SKIP_RESPONSE_HEADERS = new Set(['content-length', 'transfer-encoding']);
|
|
60
|
+
function collectSetCookieHeaders(webResponse) {
|
|
61
|
+
const seen = new Set();
|
|
62
|
+
const values = [];
|
|
63
|
+
const add = (cookie) => {
|
|
64
|
+
if (!cookie || seen.has(cookie)) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
seen.add(cookie);
|
|
68
|
+
values.push(cookie);
|
|
69
|
+
};
|
|
70
|
+
const headersWithGetSetCookie = webResponse.headers;
|
|
71
|
+
if (typeof headersWithGetSetCookie.getSetCookie === 'function') {
|
|
72
|
+
for (const cookie of headersWithGetSetCookie.getSetCookie()) {
|
|
73
|
+
add(cookie);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
webResponse.headers.forEach((value, name) => {
|
|
78
|
+
if (name.toLowerCase() === 'set-cookie') {
|
|
79
|
+
add(value);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return values;
|
|
84
|
+
}
|
|
60
85
|
/**
|
|
61
86
|
* Applies a Web API Response to a PikkuHTTPResponse.
|
|
62
87
|
* Copies status, headers (including Set-Cookie), redirects, and body.
|
|
63
88
|
*/
|
|
64
89
|
export async function applyWebResponse(res, webResponse) {
|
|
65
90
|
res.status(webResponse.status);
|
|
91
|
+
const setCookieValues = collectSetCookieHeaders(webResponse);
|
|
66
92
|
webResponse.headers.forEach((value, name) => {
|
|
67
93
|
const lower = name.toLowerCase();
|
|
68
94
|
if (SKIP_RESPONSE_HEADERS.has(lower)) {
|
|
69
95
|
return;
|
|
70
96
|
}
|
|
97
|
+
if (lower === 'set-cookie') {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
71
100
|
if (lower === 'location') {
|
|
72
101
|
res.redirect(value, webResponse.status);
|
|
73
102
|
}
|
|
@@ -75,6 +104,9 @@ export async function applyWebResponse(res, webResponse) {
|
|
|
75
104
|
res.header(name, value);
|
|
76
105
|
}
|
|
77
106
|
});
|
|
107
|
+
if (setCookieValues.length > 0) {
|
|
108
|
+
res.header('Set-Cookie', setCookieValues);
|
|
109
|
+
}
|
|
78
110
|
const body = await webResponse.text();
|
|
79
111
|
if (body) {
|
|
80
112
|
if (res.send) {
|
|
@@ -18,10 +18,11 @@ export declare class ContextAwareRPCService {
|
|
|
18
18
|
private services;
|
|
19
19
|
private wire;
|
|
20
20
|
private options;
|
|
21
|
+
private packageName;
|
|
21
22
|
constructor(services: CoreServices, wire: PikkuWire, options: {
|
|
22
23
|
requiresAuth?: boolean;
|
|
23
24
|
sessionService?: SessionService<CoreUserSession>;
|
|
24
|
-
});
|
|
25
|
+
}, packageName?: string | null);
|
|
25
26
|
rpcExposed(funcName: string, data: any): Promise<any>;
|
|
26
27
|
rpc<In = any, Out = any>(funcName: string, data: In): Promise<Out>;
|
|
27
28
|
/**
|
|
@@ -96,7 +97,7 @@ export declare class PikkuRPCService<Services extends CoreServices, TypedRPC = P
|
|
|
96
97
|
getContextRPCService(services: Services, wire: PikkuWire, requiresAuthOrOptions?: boolean | {
|
|
97
98
|
requiresAuth?: boolean;
|
|
98
99
|
sessionService?: SessionService<CoreUserSession>;
|
|
99
|
-
} | undefined, depth?: number): TypedRPC;
|
|
100
|
+
} | undefined, depth?: number, packageName?: string | null): TypedRPC;
|
|
100
101
|
}
|
|
101
102
|
export declare const rpcService: PikkuRPCService<import("../../types/core.types.js").CoreSingletonServices<{
|
|
102
103
|
logLevel?: import("../../services/logger.js").LogLevel;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { runPikkuFunc } from '../../function/function-runner.js';
|
|
2
2
|
import { pikkuState } from '../../pikku-state.js';
|
|
3
|
-
import { ForbiddenError } from '../../errors/errors.js';
|
|
4
3
|
import { PikkuError, addError } from '../../errors/error-handler.js';
|
|
5
4
|
import { parseVersionedId } from '../../version.js';
|
|
6
5
|
export class RPCNotFoundError extends PikkuError {
|
|
@@ -39,7 +38,22 @@ export const resolveNamespace = (namespacedFunction) => {
|
|
|
39
38
|
addonConfig: pkgConfig,
|
|
40
39
|
};
|
|
41
40
|
};
|
|
42
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Resolve a bare (non-namespaced) RPC name to its pikkuFuncId, preferring
|
|
43
|
+
* the caller's addon package when provided. Returns the function name plus
|
|
44
|
+
* the package scope that resolved it (null = root), so callers can thread
|
|
45
|
+
* the scope into runPikkuFunc without a second lookup.
|
|
46
|
+
*/
|
|
47
|
+
const resolvePikkuFunction = (rpcName, packageName = null) => {
|
|
48
|
+
// Addon-scoped calls: try the caller's package function meta first.
|
|
49
|
+
// (RPC meta only lives in root; addon functions are registered under their package.)
|
|
50
|
+
if (packageName) {
|
|
51
|
+
const pkgFunctions = pikkuState(packageName, 'function', 'meta');
|
|
52
|
+
const pkgMeta = pkgFunctions?.[rpcName];
|
|
53
|
+
if (pkgMeta) {
|
|
54
|
+
return { pikkuFuncId: pkgMeta.pikkuFuncId || rpcName, packageName };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
43
57
|
const rpc = pikkuState(null, 'rpc', 'meta');
|
|
44
58
|
let rpcMeta = rpc[rpcName];
|
|
45
59
|
if (!rpcMeta) {
|
|
@@ -48,20 +62,32 @@ const getPikkuFunctionName = (rpcName) => {
|
|
|
48
62
|
rpcMeta = rpc[baseName];
|
|
49
63
|
}
|
|
50
64
|
}
|
|
65
|
+
if (!rpcMeta) {
|
|
66
|
+
const rootFunctions = pikkuState(null, 'function', 'meta');
|
|
67
|
+
const rootFunctionMeta = rootFunctions?.[rpcName];
|
|
68
|
+
if (rootFunctionMeta) {
|
|
69
|
+
return {
|
|
70
|
+
pikkuFuncId: rootFunctionMeta.pikkuFuncId || rpcName,
|
|
71
|
+
packageName: null,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
51
75
|
if (!rpcMeta) {
|
|
52
76
|
throw new RPCNotFoundError(rpcName);
|
|
53
77
|
}
|
|
54
|
-
return rpcMeta;
|
|
78
|
+
return { pikkuFuncId: rpcMeta, packageName: null };
|
|
55
79
|
};
|
|
56
80
|
// Context-aware RPC client for use within services
|
|
57
81
|
export class ContextAwareRPCService {
|
|
58
82
|
services;
|
|
59
83
|
wire;
|
|
60
84
|
options;
|
|
61
|
-
|
|
85
|
+
packageName;
|
|
86
|
+
constructor(services, wire, options, packageName = null) {
|
|
62
87
|
this.services = services;
|
|
63
88
|
this.wire = wire;
|
|
64
89
|
this.options = options;
|
|
90
|
+
this.packageName = packageName;
|
|
65
91
|
}
|
|
66
92
|
async rpcExposed(funcName, data) {
|
|
67
93
|
let functionMeta;
|
|
@@ -72,13 +98,14 @@ export class ContextAwareRPCService {
|
|
|
72
98
|
}
|
|
73
99
|
}
|
|
74
100
|
else {
|
|
75
|
-
|
|
101
|
+
const resolved = resolvePikkuFunction(funcName, this.packageName);
|
|
102
|
+
functionMeta = pikkuState(resolved.packageName, 'function', 'meta')[resolved.pikkuFuncId];
|
|
76
103
|
}
|
|
77
104
|
if (!functionMeta) {
|
|
78
105
|
throw new RPCNotFoundError(funcName);
|
|
79
106
|
}
|
|
80
107
|
if (!functionMeta.expose) {
|
|
81
|
-
throw new
|
|
108
|
+
throw new RPCNotFoundError(funcName);
|
|
82
109
|
}
|
|
83
110
|
return await this.rpc(funcName, data);
|
|
84
111
|
}
|
|
@@ -105,13 +132,18 @@ export class ContextAwareRPCService {
|
|
|
105
132
|
// Not an addon — fall through to local lookup
|
|
106
133
|
}
|
|
107
134
|
}
|
|
108
|
-
//
|
|
135
|
+
// Bare name: resolve via caller's package scope first (if any), then root.
|
|
136
|
+
// Note: intra-addon bare calls do NOT re-apply the addon's external
|
|
137
|
+
// addonConfig.auth/tags — those gates are only applied on the external
|
|
138
|
+
// 'namespace:func' boundary via invokeAddonFunction.
|
|
109
139
|
try {
|
|
110
|
-
|
|
140
|
+
const resolved = resolvePikkuFunction(funcName, this.packageName);
|
|
141
|
+
return await runPikkuFunc('rpc', funcName, resolved.pikkuFuncId, {
|
|
111
142
|
auth: this.options.requiresAuth,
|
|
112
143
|
singletonServices: this.services,
|
|
113
144
|
data: () => data,
|
|
114
145
|
wire: updatedWire,
|
|
146
|
+
packageName: resolved.packageName,
|
|
115
147
|
});
|
|
116
148
|
}
|
|
117
149
|
catch (e) {
|
|
@@ -181,11 +213,13 @@ export class ContextAwareRPCService {
|
|
|
181
213
|
return this.invokeAddonFunction(rpcName, data, mergedWire);
|
|
182
214
|
}
|
|
183
215
|
try {
|
|
184
|
-
|
|
216
|
+
const resolved = resolvePikkuFunction(rpcName, this.packageName);
|
|
217
|
+
return await runPikkuFunc('rpc', rpcName, resolved.pikkuFuncId, {
|
|
185
218
|
auth: this.options.requiresAuth,
|
|
186
219
|
singletonServices: this.services,
|
|
187
220
|
data: () => data,
|
|
188
221
|
wire: mergedWire,
|
|
222
|
+
packageName: resolved.packageName,
|
|
189
223
|
});
|
|
190
224
|
}
|
|
191
225
|
catch (e) {
|
|
@@ -269,12 +303,12 @@ export class ContextAwareRPCService {
|
|
|
269
303
|
// RPC Service class for the global interface
|
|
270
304
|
export class PikkuRPCService {
|
|
271
305
|
// Convenience function for initializing
|
|
272
|
-
getContextRPCService(services, wire, requiresAuthOrOptions, depth = 0) {
|
|
306
|
+
getContextRPCService(services, wire, requiresAuthOrOptions, depth = 0, packageName = null) {
|
|
273
307
|
const options = typeof requiresAuthOrOptions === 'object' &&
|
|
274
308
|
requiresAuthOrOptions !== null
|
|
275
309
|
? requiresAuthOrOptions
|
|
276
310
|
: { requiresAuth: requiresAuthOrOptions };
|
|
277
|
-
const serviceRPC = new ContextAwareRPCService(services, wire, options);
|
|
311
|
+
const serviceRPC = new ContextAwareRPCService(services, wire, options, packageName);
|
|
278
312
|
return {
|
|
279
313
|
depth,
|
|
280
314
|
global: false,
|
|
@@ -26,6 +26,10 @@ export interface GraphNodeDef<RPCMap extends Record<string, RPCHandler>, NodeIds
|
|
|
26
26
|
next?: NextConfig<NodeIds>;
|
|
27
27
|
/** Error handling - node(s) to execute on error */
|
|
28
28
|
onError?: NodeIds | NodeIds[];
|
|
29
|
+
/** Maximum retry attempts allowed (excludes the initial attempt) */
|
|
30
|
+
retries?: number;
|
|
31
|
+
/** Delay between retries — milliseconds, duration string, or 'exponential' */
|
|
32
|
+
retryDelay?: string | number;
|
|
29
33
|
}
|
|
30
34
|
/**
|
|
31
35
|
* Helper to create a type-safe graph definition with RPC autocomplete.
|
|
@@ -87,6 +91,8 @@ type GraphNodeConfigMap<FuncMap extends Record<string, string>, RPCMap extends R
|
|
|
87
91
|
next?: NextConfig<Extract<keyof FuncMap, string>>;
|
|
88
92
|
input?: (ref: <N extends Extract<keyof FuncMap, string>, P extends keyof ComputeNodeOutputs<FuncMap, RPCMap>[N] & string>(nodeId: N, path: P) => TypedRef<ComputeNodeOutputs<FuncMap, RPCMap>[N][P]>) => InputWithRefs<ComputeNodeInputs<FuncMap, RPCMap>[K]>;
|
|
89
93
|
onError?: Extract<keyof FuncMap, string> | Extract<keyof FuncMap, string>[];
|
|
94
|
+
retries?: number;
|
|
95
|
+
retryDelay?: string | number;
|
|
90
96
|
};
|
|
91
97
|
};
|
|
92
98
|
/**
|
|
@@ -113,5 +119,7 @@ export declare function graph<NodeIds extends string = string>(nodes: Record<Nod
|
|
|
113
119
|
next?: NextConfig<NodeIds>;
|
|
114
120
|
input?: (ref: (nodeId: NodeIds, path: string) => RefValue) => Record<string, unknown>;
|
|
115
121
|
onError?: NodeIds | NodeIds[];
|
|
122
|
+
retries?: number;
|
|
123
|
+
retryDelay?: string | number;
|
|
116
124
|
}>): Record<NodeIds, GraphNodeConfig<NodeIds>>;
|
|
117
125
|
export {};
|
|
@@ -20,6 +20,8 @@ export function createGraph() {
|
|
|
20
20
|
input: def?.input,
|
|
21
21
|
next: def?.next,
|
|
22
22
|
onError: def?.onError,
|
|
23
|
+
retries: def?.retries,
|
|
24
|
+
retryDelay: def?.retryDelay,
|
|
23
25
|
};
|
|
24
26
|
}
|
|
25
27
|
return result;
|
|
@@ -52,6 +54,8 @@ export function graph(nodes) {
|
|
|
52
54
|
input: def.input,
|
|
53
55
|
next: def.next,
|
|
54
56
|
onError: def.onError,
|
|
57
|
+
retries: def.retries,
|
|
58
|
+
retryDelay: def.retryDelay,
|
|
55
59
|
};
|
|
56
60
|
}
|
|
57
61
|
return result;
|
|
@@ -257,11 +257,13 @@ function areDependenciesSatisfied(node, completedNodeIds) {
|
|
|
257
257
|
const deps = extractReferencedNodeIds(node.input).filter((id) => !IGNORED_REFS.has(id));
|
|
258
258
|
return deps.every((dep) => completedNodeIds.has(dep));
|
|
259
259
|
}
|
|
260
|
-
async function queueGraphNode(workflowService, runId, _graphName, nodeId, rpcName, input) {
|
|
261
|
-
|
|
262
|
-
retries: 0,
|
|
263
|
-
|
|
264
|
-
|
|
260
|
+
async function queueGraphNode(workflowService, runId, _graphName, nodeId, rpcName, input, nodeConfig) {
|
|
261
|
+
const stepOptions = {
|
|
262
|
+
retries: nodeConfig?.retries ?? 0,
|
|
263
|
+
retryDelay: nodeConfig?.retryDelay,
|
|
264
|
+
};
|
|
265
|
+
await workflowService.insertStepState(runId, nodeId, rpcName, input, stepOptions);
|
|
266
|
+
await workflowService.queueStepWorker(runId, nodeId, rpcName, input, stepOptions);
|
|
265
267
|
}
|
|
266
268
|
export async function continueGraph(workflowService, runId, graphName, overrideMeta) {
|
|
267
269
|
const meta = overrideMeta ?? getWorkflowMeta(graphName);
|
|
@@ -332,7 +334,7 @@ export async function continueGraph(workflowService, runId, graphName, overrideM
|
|
|
332
334
|
const fetchedResults = await workflowService.getNodeResults(runId, referencedNodeIds);
|
|
333
335
|
const nodeResults = { trigger: triggerInput, ...fetchedResults };
|
|
334
336
|
const resolvedInput = resolveSerializedInput(node.input, nodeResults);
|
|
335
|
-
await queueGraphNode(workflowService, runId, graphName, nodeId, node.rpcName, resolvedInput);
|
|
337
|
+
await queueGraphNode(workflowService, runId, graphName, nodeId, node.rpcName, resolvedInput, node);
|
|
336
338
|
}
|
|
337
339
|
}
|
|
338
340
|
export async function executeGraphStep(workflowService, rpcService, runId, stepId, nodeId, rpcName, data, graphName) {
|
|
@@ -410,7 +412,7 @@ export async function executeGraphStep(workflowService, rpcService, runId, stepI
|
|
|
410
412
|
for (const errorNodeId of errorNodes) {
|
|
411
413
|
const errorNode = meta.nodes[errorNodeId];
|
|
412
414
|
if (errorNode) {
|
|
413
|
-
await queueGraphNode(workflowService, runId, graphName, errorNodeId, errorNode.rpcName, { error: { message: error.message } });
|
|
415
|
+
await queueGraphNode(workflowService, runId, graphName, errorNodeId, errorNode.rpcName, { error: { message: error.message } }, errorNode);
|
|
414
416
|
}
|
|
415
417
|
}
|
|
416
418
|
throw error;
|
|
@@ -430,7 +432,7 @@ async function executeGraphNodeInline(workflowService, rpcService, runId, graphN
|
|
|
430
432
|
if (!node)
|
|
431
433
|
return;
|
|
432
434
|
const rpcName = node.rpcName;
|
|
433
|
-
const stepState = await workflowService.insertStepState(runId, nodeId, rpcName, input, { retries:
|
|
435
|
+
const stepState = await workflowService.insertStepState(runId, nodeId, rpcName, input, { retries: node.retries ?? 0, retryDelay: node.retryDelay });
|
|
434
436
|
await workflowService.setStepRunning(stepState.stepId);
|
|
435
437
|
const wireState = {};
|
|
436
438
|
const graphWire = {
|
|
@@ -486,7 +488,7 @@ async function executeGraphNodeInline(workflowService, rpcService, runId, graphN
|
|
|
486
488
|
message: `RPC '${rpcName}' not found. Deploy the missing function and resume.`,
|
|
487
489
|
code: 'RPC_NOT_FOUND',
|
|
488
490
|
});
|
|
489
|
-
|
|
491
|
+
throw new WorkflowSuspendedException(runId, 'RPC_NOT_FOUND');
|
|
490
492
|
}
|
|
491
493
|
await workflowService.setStepError(stepState.stepId, error);
|
|
492
494
|
if (node?.onError) {
|
|
@@ -627,7 +629,7 @@ export async function runWorkflowGraph(workflowService, graphName, triggerInput,
|
|
|
627
629
|
const resolvedInput = node.input && Object.keys(node.input).length > 0
|
|
628
630
|
? resolveSerializedInput(node.input, triggerNodeResults)
|
|
629
631
|
: triggerInput;
|
|
630
|
-
await queueGraphNode(workflowService, runId, graphName, nodeId, node.rpcName, resolvedInput);
|
|
632
|
+
await queueGraphNode(workflowService, runId, graphName, nodeId, node.rpcName, resolvedInput, node);
|
|
631
633
|
}
|
|
632
634
|
if (inline) {
|
|
633
635
|
workflowService.unregisterInlineRun(runId);
|
|
@@ -45,6 +45,10 @@ export interface GraphNodeConfig<NodeIds extends string = string> {
|
|
|
45
45
|
next?: NextConfig<NodeIds>;
|
|
46
46
|
/** Error routing - node(s) to execute on error */
|
|
47
47
|
onError?: NodeIds | NodeIds[];
|
|
48
|
+
/** Maximum retry attempts allowed (excludes the initial attempt) */
|
|
49
|
+
retries?: number;
|
|
50
|
+
/** Delay between retries — milliseconds, duration string, or 'exponential' */
|
|
51
|
+
retryDelay?: string | number;
|
|
48
52
|
}
|
|
49
53
|
/**
|
|
50
54
|
* Graph wire context - available to functions running in a workflow graph
|
|
@@ -8,5 +8,5 @@ export { pikkuWorkflowGraph, type PikkuWorkflowGraphConfig, type PikkuWorkflowGr
|
|
|
8
8
|
export { validateWorkflowWiring, computeEntryNodeIds, } from './graph/graph-validation.js';
|
|
9
9
|
export { pikkuWorkflowWorkerFunc, pikkuWorkflowOrchestratorFunc, pikkuWorkflowSleeperFunc, } from './workflow-queue-workers.js';
|
|
10
10
|
export type { WorkflowStepInput as WorkflowStepQueueInput, PikkuWorkflowOrchestratorInput, PikkuWorkflowSleeperInput, } from './workflow-queue-workers.js';
|
|
11
|
-
export type { WorkflowService, WorkflowServiceConfig, WorkflowPlannedStep, WorkflowRunWire, WorkflowStatus, WorkflowVersionStatus, StepStatus, WorkflowRun, WorkflowRunStatus, StepState, WorkflowRunService, CoreWorkflow, PikkuWorkflow, ContextVariable, WorkflowContext, WorkflowsMeta, WorkflowRuntimeMeta, WorkflowsRuntimeMeta, WorkflowStepInput, WorkflowOrchestratorInput, WorkflowSleeperInput, } from './workflow.types.js';
|
|
11
|
+
export type { WorkflowService, WorkflowServiceConfig, WorkflowPlannedStep, WorkflowRunWire, WorkflowStatus, WorkflowVersionStatus, StepStatus, WorkflowRun, WorkflowRunStatus, StepState, WorkflowRunService, WorkflowRunMirror, CoreWorkflow, PikkuWorkflow, ContextVariable, WorkflowContext, WorkflowsMeta, WorkflowRuntimeMeta, WorkflowsRuntimeMeta, WorkflowStepInput, WorkflowOrchestratorInput, WorkflowSleeperInput, } from './workflow.types.js';
|
|
12
12
|
export type { WorkflowStepOptions, WorkflowWireDoRPC, WorkflowWireDoInline, WorkflowWireSleep, WorkflowWireSuspend, InputSource, OutputBinding, RpcStepMeta, SimpleCondition, Condition, BranchCase, BranchStepMeta, ParallelGroupStepMeta, FanoutStepMeta, ReturnStepMeta, InlineStepMeta, SleepStepMeta, CancelStepMeta, SetStepMeta, SwitchCaseMeta, SwitchStepMeta, FilterStepMeta, ArrayPredicateStepMeta, WorkflowStepMeta, WorkflowStepWire, PikkuWorkflowWire, } from './workflow.types.js';
|