@pikku/core 0.12.13 → 0.12.15
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 +46 -0
- package/dist/errors/errors.d.ts +18 -0
- package/dist/errors/errors.js +33 -0
- package/dist/function/function-runner.d.ts +1 -1
- package/dist/function/function-runner.js +10 -5
- package/dist/function/functions.types.js +1 -0
- package/dist/function/index.d.ts +2 -1
- package/dist/function/index.js +1 -0
- package/dist/handle-error.d.ts +2 -2
- package/dist/handle-error.js +8 -8
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/middleware/index.d.ts +2 -0
- package/dist/middleware/index.js +2 -0
- package/dist/middleware/remote-auth.js +5 -2
- package/dist/permissions.d.ts +13 -1
- package/dist/permissions.js +51 -0
- package/dist/pikku-state.d.ts +0 -1
- package/dist/pikku-state.js +2 -4
- package/dist/remote.d.ts +10 -0
- package/dist/remote.js +32 -0
- package/dist/schema.js +2 -0
- package/dist/services/credential-service.d.ts +11 -0
- package/dist/services/credential-wire-service.d.ts +14 -3
- package/dist/services/credential-wire-service.js +49 -6
- package/dist/services/deployment-service.d.ts +12 -1
- package/dist/services/in-memory-queue-service.d.ts +7 -0
- package/dist/services/in-memory-queue-service.js +28 -0
- package/dist/services/index.d.ts +4 -1
- package/dist/services/index.js +2 -1
- package/dist/services/local-credential-service.d.ts +2 -0
- package/dist/services/local-credential-service.js +20 -0
- package/dist/services/logger-console.d.ts +26 -40
- package/dist/services/logger-console.js +102 -46
- package/dist/services/logger.d.ts +5 -0
- package/dist/services/meta-service.d.ts +175 -0
- package/dist/services/meta-service.js +310 -0
- package/dist/services/pikku-user-id.d.ts +3 -0
- package/dist/services/pikku-user-id.js +16 -0
- package/dist/services/typed-credential-service.d.ts +2 -0
- package/dist/services/typed-credential-service.js +6 -0
- package/dist/services/workflow-service.d.ts +6 -1
- package/dist/testing/service-tests.js +0 -8
- package/dist/types/core.types.d.ts +14 -2
- package/dist/types/state.types.d.ts +9 -2
- package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +21 -0
- package/dist/wirings/ai-agent/ai-agent-prepare.js +90 -21
- package/dist/wirings/ai-agent/ai-agent-registry.js +2 -2
- package/dist/wirings/ai-agent/ai-agent-runner.js +24 -2
- package/dist/wirings/ai-agent/ai-agent-stream.d.ts +2 -1
- package/dist/wirings/ai-agent/ai-agent-stream.js +61 -3
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +25 -4
- package/dist/wirings/ai-agent/index.d.ts +1 -1
- package/dist/wirings/ai-agent/index.js +1 -1
- package/dist/wirings/channel/channel-runner.js +3 -3
- package/dist/wirings/cli/cli-runner.js +4 -3
- package/dist/wirings/cli/command-parser.js +7 -3
- package/dist/wirings/http/http-runner.d.ts +1 -1
- package/dist/wirings/http/http-runner.js +23 -15
- package/dist/wirings/http/http.types.d.ts +2 -0
- package/dist/wirings/mcp/mcp-runner.js +5 -3
- package/dist/wirings/queue/queue-runner.d.ts +3 -1
- package/dist/wirings/queue/queue-runner.js +8 -3
- package/dist/wirings/queue/queue.types.d.ts +6 -0
- package/dist/wirings/rpc/rpc-runner.js +57 -90
- package/dist/wirings/scheduler/scheduler-runner.d.ts +3 -1
- package/dist/wirings/scheduler/scheduler-runner.js +9 -5
- package/dist/wirings/trigger/trigger-runner.js +4 -2
- package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +2 -0
- package/dist/wirings/workflow/dsl/workflow-runner.d.ts +1 -1
- package/dist/wirings/workflow/dsl/workflow-runner.js +6 -9
- package/dist/wirings/workflow/graph/graph-runner.d.ts +1 -1
- package/dist/wirings/workflow/graph/graph-runner.js +18 -4
- package/dist/wirings/workflow/index.d.ts +4 -2
- package/dist/wirings/workflow/index.js +4 -2
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +16 -4
- package/dist/wirings/workflow/pikku-workflow-service.js +218 -24
- package/dist/wirings/workflow/workflow-queue-workers.d.ts +40 -0
- package/dist/wirings/workflow/workflow-queue-workers.js +41 -0
- package/dist/wirings/workflow/workflow.types.d.ts +19 -1
- package/package.json +4 -1
- package/src/errors/errors.ts +44 -0
- package/src/function/function-runner.ts +24 -13
- package/src/function/functions.types.ts +1 -0
- package/src/function/index.ts +10 -0
- package/src/handle-error.test.ts +2 -2
- package/src/handle-error.ts +8 -8
- package/src/index.ts +1 -2
- package/src/middleware/index.ts +2 -0
- package/src/middleware/remote-auth.test.ts +4 -4
- package/src/middleware/remote-auth.ts +7 -2
- package/src/permissions.ts +70 -0
- package/src/pikku-state.test.ts +0 -26
- package/src/pikku-state.ts +2 -5
- package/src/remote.ts +46 -0
- package/src/schema.ts +1 -0
- package/src/services/credential-service.ts +13 -0
- package/src/services/credential-wire-service.test.ts +174 -0
- package/src/services/credential-wire-service.ts +52 -8
- package/src/services/deployment-service.ts +17 -1
- package/src/services/in-memory-queue-service.ts +46 -0
- package/src/services/index.ts +21 -1
- package/src/services/local-credential-service.ts +22 -0
- package/src/services/logger-console.ts +127 -47
- package/src/services/logger.ts +6 -0
- package/src/services/meta-service.ts +523 -0
- package/src/services/pikku-user-id.test.ts +62 -0
- package/src/services/pikku-user-id.ts +17 -0
- package/src/services/typed-credential-service.ts +8 -0
- package/src/services/workflow-service.ts +8 -0
- package/src/testing/service-tests.ts +0 -10
- package/src/types/core.types.ts +16 -2
- package/src/types/state.types.ts +7 -2
- package/src/wirings/ai-agent/ai-agent-prepare.ts +122 -41
- package/src/wirings/ai-agent/ai-agent-registry.ts +3 -3
- package/src/wirings/ai-agent/ai-agent-runner.ts +22 -3
- package/src/wirings/ai-agent/ai-agent-stream.ts +115 -3
- package/src/wirings/ai-agent/ai-agent.types.ts +27 -4
- package/src/wirings/ai-agent/index.ts +1 -0
- package/src/wirings/channel/channel-handler.ts +0 -1
- package/src/wirings/channel/channel-runner.ts +4 -5
- package/src/wirings/cli/cli-runner.test.ts +8 -7
- package/src/wirings/cli/cli-runner.ts +5 -4
- package/src/wirings/cli/command-parser.ts +8 -3
- package/src/wirings/http/http-runner.ts +27 -18
- package/src/wirings/http/http.types.ts +2 -0
- package/src/wirings/mcp/mcp-runner.ts +7 -10
- package/src/wirings/queue/queue-runner.test.ts +5 -11
- package/src/wirings/queue/queue-runner.ts +12 -4
- package/src/wirings/queue/queue.types.ts +6 -0
- package/src/wirings/rpc/rpc-runner.ts +91 -118
- package/src/wirings/scheduler/scheduler-runner.test.ts +5 -11
- package/src/wirings/scheduler/scheduler-runner.ts +13 -5
- package/src/wirings/trigger/trigger-runner.test.ts +10 -11
- package/src/wirings/trigger/trigger-runner.ts +6 -4
- package/src/wirings/workflow/dsl/workflow-dsl.types.ts +2 -0
- package/src/wirings/workflow/dsl/workflow-runner.ts +11 -10
- package/src/wirings/workflow/graph/graph-runner.ts +19 -4
- package/src/wirings/workflow/index.ts +17 -6
- package/src/wirings/workflow/pikku-workflow-service.ts +286 -24
- package/src/wirings/workflow/workflow-queue-workers.ts +85 -0
- package/src/wirings/workflow/workflow.types.ts +18 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/wirings/ai-agent/agent-dynamic-workflow.d.ts +0 -6
- package/dist/wirings/ai-agent/agent-dynamic-workflow.js +0 -468
- package/dist/wirings/workflow/workflow-helpers.d.ts +0 -36
- package/dist/wirings/workflow/workflow-helpers.js +0 -38
- package/src/wirings/ai-agent/agent-dynamic-workflow.ts +0 -610
- package/src/wirings/workflow/workflow-helpers.test.ts +0 -129
- package/src/wirings/workflow/workflow-helpers.ts +0 -79
|
@@ -369,15 +369,16 @@ describe('CLI Runner', () => {
|
|
|
369
369
|
assert.strictEqual(programs['my-cli'].middleware[0], middleware)
|
|
370
370
|
})
|
|
371
371
|
|
|
372
|
-
test('should
|
|
372
|
+
test('should skip when CLI metadata not found', () => {
|
|
373
373
|
pikkuState(null, 'cli', 'meta', { programs: {}, renderers: {} })
|
|
374
374
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
375
|
+
wireCLI({
|
|
376
|
+
program: 'nonexistent',
|
|
377
|
+
commands: {},
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
const programs = pikkuState(null, 'cli', 'programs')
|
|
381
|
+
assert.strictEqual(programs['nonexistent'], undefined)
|
|
381
382
|
})
|
|
382
383
|
})
|
|
383
384
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NotFoundError
|
|
1
|
+
import { NotFoundError } from '../../errors/errors.js'
|
|
2
2
|
import { addFunction, runPikkuFunc } from '../../function/function-runner.js'
|
|
3
3
|
import { pikkuState } from '../../pikku-state.js'
|
|
4
4
|
import type {
|
|
@@ -66,9 +66,10 @@ export const wireCLI = <
|
|
|
66
66
|
const cliMeta = pikkuState(null, 'cli', 'meta') || {}
|
|
67
67
|
|
|
68
68
|
if (!cliMeta.programs?.[cli.program]) {
|
|
69
|
-
|
|
70
|
-
`
|
|
69
|
+
console.warn(
|
|
70
|
+
`[pikku] Skipping CLI program '${cli.program}' — metadata not found. Consider moving this wiring to its own file.`
|
|
71
71
|
)
|
|
72
|
+
return
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
// Get existing programs state and add this program
|
|
@@ -191,7 +192,7 @@ function registerCLICommands(
|
|
|
191
192
|
}
|
|
192
193
|
}
|
|
193
194
|
|
|
194
|
-
addFunction(funcName, unwrapFunc(command))
|
|
195
|
+
addFunction(funcName, unwrapFunc(command), currentMeta?.packageName)
|
|
195
196
|
|
|
196
197
|
// Register renderer if provided
|
|
197
198
|
if (typeof command === 'object' && command.render) {
|
|
@@ -6,6 +6,11 @@ import type {
|
|
|
6
6
|
CLIOption,
|
|
7
7
|
} from './cli.types.js'
|
|
8
8
|
|
|
9
|
+
/** Convert kebab-case to camelCase: "from-plan" → "fromPlan" */
|
|
10
|
+
function toCamelCase(str: string): string {
|
|
11
|
+
return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase())
|
|
12
|
+
}
|
|
13
|
+
|
|
9
14
|
/**
|
|
10
15
|
* Result of parsing CLI arguments
|
|
11
16
|
*/
|
|
@@ -117,11 +122,11 @@ export function parseCLIArguments(
|
|
|
117
122
|
const arg = args[currentIndex]
|
|
118
123
|
|
|
119
124
|
if (arg.startsWith('--')) {
|
|
120
|
-
// Long option
|
|
125
|
+
// Long option (--from-plan → fromPlan)
|
|
121
126
|
const equalIndex = arg.indexOf('=')
|
|
122
127
|
if (equalIndex > 0) {
|
|
123
128
|
// --option=value format
|
|
124
|
-
const key = arg.slice(2, equalIndex)
|
|
129
|
+
const key = toCamelCase(arg.slice(2, equalIndex))
|
|
125
130
|
const optionDef = availableOptions[key]
|
|
126
131
|
|
|
127
132
|
// Unknown options are allowed for forward compatibility
|
|
@@ -129,7 +134,7 @@ export function parseCLIArguments(
|
|
|
129
134
|
optionArgs[key] = parseOptionValue(value, optionDef)
|
|
130
135
|
} else {
|
|
131
136
|
// --option value format
|
|
132
|
-
const key = arg.slice(2)
|
|
137
|
+
const key = toCamelCase(arg.slice(2))
|
|
133
138
|
const optionDef = availableOptions[key]
|
|
134
139
|
|
|
135
140
|
// Unknown options are allowed for forward compatibility
|
|
@@ -21,7 +21,7 @@ import type {
|
|
|
21
21
|
PikkuWire,
|
|
22
22
|
PikkuWiringTypes,
|
|
23
23
|
} from '../../types/core.types.js'
|
|
24
|
-
import { NotFoundError
|
|
24
|
+
import { NotFoundError } from '../../errors/errors.js'
|
|
25
25
|
import {
|
|
26
26
|
closeWireServices,
|
|
27
27
|
createWeakUID,
|
|
@@ -33,10 +33,6 @@ import {
|
|
|
33
33
|
} from '../../pikku-state.js'
|
|
34
34
|
import { PikkuSessionService } from '../../services/user-session-service.js'
|
|
35
35
|
import { getErrorResponse } from '../../errors/error-handler.js'
|
|
36
|
-
import {
|
|
37
|
-
PikkuCredentialWireService,
|
|
38
|
-
createMiddlewareCredentialWireProps,
|
|
39
|
-
} from '../../services/credential-wire-service.js'
|
|
40
36
|
import { handleHTTPError } from '../../handle-error.js'
|
|
41
37
|
import { pikkuState } from '../../pikku-state.js'
|
|
42
38
|
import { PikkuFetchHTTPResponse } from './pikku-fetch-http-response.js'
|
|
@@ -184,9 +180,14 @@ export const wireHTTP = <
|
|
|
184
180
|
const httpMeta = pikkuState(null, 'http', 'meta')
|
|
185
181
|
const routeMeta = httpMeta[httpWiring.method][httpWiring.route]
|
|
186
182
|
if (!routeMeta) {
|
|
187
|
-
|
|
188
|
-
|
|
183
|
+
// In deploy units with filtered metadata, wiring files may include
|
|
184
|
+
// routes for functions not in this unit. This happens when multiple
|
|
185
|
+
// wirings share a file — split them into separate files for better
|
|
186
|
+
// tree-shaking.
|
|
187
|
+
console.warn(
|
|
188
|
+
`[pikku] Skipping HTTP route '${httpWiring.method.toUpperCase()} ${httpWiring.route}' — metadata not found. Consider moving this wiring to its own file.`
|
|
189
189
|
)
|
|
190
|
+
return
|
|
190
191
|
}
|
|
191
192
|
if (httpWiring.func) {
|
|
192
193
|
addFunction(routeMeta.pikkuFuncId, httpWiring.func)
|
|
@@ -303,7 +304,6 @@ const executeRoute = async (
|
|
|
303
304
|
}
|
|
304
305
|
) => {
|
|
305
306
|
const userSession = new PikkuSessionService<CoreUserSession>()
|
|
306
|
-
const credentialWire = new PikkuCredentialWireService()
|
|
307
307
|
const { params, route, meta } = matchedRoute
|
|
308
308
|
const { singletonServices, createWireServices, skipUserSession, requestId } =
|
|
309
309
|
services
|
|
@@ -376,13 +376,13 @@ const executeRoute = async (
|
|
|
376
376
|
}
|
|
377
377
|
|
|
378
378
|
const wire: PikkuWire = {
|
|
379
|
+
traceId: requestId,
|
|
379
380
|
http,
|
|
380
381
|
channel,
|
|
381
382
|
session: userSession.get() as CoreUserSession | undefined,
|
|
382
383
|
setSession: (s: any) => userSession.setInitial(s),
|
|
383
384
|
getSession: () => userSession.get(),
|
|
384
385
|
hasSessionChanged: () => userSession.sessionChanged,
|
|
385
|
-
...createMiddlewareCredentialWireProps(credentialWire),
|
|
386
386
|
}
|
|
387
387
|
|
|
388
388
|
result = await runPikkuFunc(
|
|
@@ -402,7 +402,6 @@ const executeRoute = async (
|
|
|
402
402
|
tags: route.tags,
|
|
403
403
|
wire,
|
|
404
404
|
sessionService: userSession,
|
|
405
|
-
credentialWireService: credentialWire,
|
|
406
405
|
packageName: meta.packageName,
|
|
407
406
|
}
|
|
408
407
|
)
|
|
@@ -496,6 +495,7 @@ export const fetchData = async <In, Out>(
|
|
|
496
495
|
bubbleErrors = false,
|
|
497
496
|
exposeErrors = false,
|
|
498
497
|
generateRequestId,
|
|
498
|
+
traceId: externalTraceId,
|
|
499
499
|
}: RunHTTPWiringOptions = {}
|
|
500
500
|
): Promise<Out | void> => {
|
|
501
501
|
const singletonServices = getSingletonServices()
|
|
@@ -506,11 +506,20 @@ export const fetchData = async <In, Out>(
|
|
|
506
506
|
// Combine the request and response into one wire object
|
|
507
507
|
const pikkuRequest =
|
|
508
508
|
request instanceof Request ? new PikkuFetchHTTPRequest(request) : request
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
509
|
+
|
|
510
|
+
// Resolve traceId: external (e.g. CF-Ray) > x-request-id header > generated
|
|
511
|
+
let requestId: string | null = externalTraceId ?? null
|
|
512
|
+
if (!requestId) {
|
|
513
|
+
try {
|
|
514
|
+
requestId = pikkuRequest.header('x-request-id')
|
|
515
|
+
} catch {}
|
|
516
|
+
}
|
|
513
517
|
requestId = requestId || generateRequestId?.() || createWeakUID()
|
|
518
|
+
|
|
519
|
+
// Scoped logger for HTTP runner internal logging (error handling, route matching)
|
|
520
|
+
// Functions/middleware receive singletonServices.logger directly for compatibility
|
|
521
|
+
const scopedLogger =
|
|
522
|
+
singletonServices.logger.scope?.(requestId) ?? singletonServices.logger
|
|
514
523
|
const http = createHTTPWire(pikkuRequest, response)
|
|
515
524
|
const apiType = http!.request!.method()
|
|
516
525
|
const apiRoute = http!.request!.path()
|
|
@@ -533,7 +542,7 @@ export const fetchData = async <In, Out>(
|
|
|
533
542
|
response.status(204).json(undefined as any)
|
|
534
543
|
return
|
|
535
544
|
}
|
|
536
|
-
|
|
545
|
+
scopedLogger.info({
|
|
537
546
|
message: 'Route not found',
|
|
538
547
|
apiRoute,
|
|
539
548
|
apiType,
|
|
@@ -558,7 +567,7 @@ export const fetchData = async <In, Out>(
|
|
|
558
567
|
} catch (e: any) {
|
|
559
568
|
if (matchedRoute?.route.sse) {
|
|
560
569
|
// For SSE routes, send error through the stream since the response is already in stream mode
|
|
561
|
-
|
|
570
|
+
scopedLogger.error(e instanceof Error ? e.message : e)
|
|
562
571
|
try {
|
|
563
572
|
const errorResponse = getErrorResponse(e)
|
|
564
573
|
response.arrayBuffer(
|
|
@@ -569,7 +578,7 @@ export const fetchData = async <In, Out>(
|
|
|
569
578
|
)
|
|
570
579
|
response.arrayBuffer(JSON.stringify({ type: 'done' }))
|
|
571
580
|
} catch (streamErr: any) {
|
|
572
|
-
|
|
581
|
+
scopedLogger.error(
|
|
573
582
|
`SSE error while sending error payload: ${streamErr instanceof Error ? streamErr.message : String(streamErr)}`
|
|
574
583
|
)
|
|
575
584
|
}
|
|
@@ -579,7 +588,7 @@ export const fetchData = async <In, Out>(
|
|
|
579
588
|
e,
|
|
580
589
|
http,
|
|
581
590
|
requestId,
|
|
582
|
-
|
|
591
|
+
scopedLogger,
|
|
583
592
|
logWarningsForStatusCodes,
|
|
584
593
|
respondWith404,
|
|
585
594
|
bubbleErrors,
|
|
@@ -37,6 +37,8 @@ export type RunHTTPWiringOptions = Partial<{
|
|
|
37
37
|
bubbleErrors: boolean
|
|
38
38
|
exposeErrors: boolean
|
|
39
39
|
generateRequestId: () => string
|
|
40
|
+
/** Pre-resolved trace ID (e.g. CF-Ray). Falls back to x-request-id header or generated ID. */
|
|
41
|
+
traceId: string
|
|
40
42
|
}>
|
|
41
43
|
|
|
42
44
|
/**
|
|
@@ -20,16 +20,11 @@ import {
|
|
|
20
20
|
} from '../../pikku-state.js'
|
|
21
21
|
import { addFunction, runPikkuFunc } from '../../function/function-runner.js'
|
|
22
22
|
import { resolveNamespace } from '../rpc/rpc-runner.js'
|
|
23
|
-
import {
|
|
24
|
-
BadRequestError,
|
|
25
|
-
NotFoundError,
|
|
26
|
-
PikkuMissingMetaError,
|
|
27
|
-
} from '../../errors/errors.js'
|
|
23
|
+
import { BadRequestError, NotFoundError } from '../../errors/errors.js'
|
|
28
24
|
import {
|
|
29
25
|
PikkuSessionService,
|
|
30
26
|
createMiddlewareSessionWireProps,
|
|
31
27
|
} from '../../services/user-session-service.js'
|
|
32
|
-
|
|
33
28
|
export class MCPError extends Error {
|
|
34
29
|
constructor(public readonly error: JsonRpcErrorResponse) {
|
|
35
30
|
super(error?.message || 'MCP Error')
|
|
@@ -58,9 +53,10 @@ export const wireMCPResource = <
|
|
|
58
53
|
const resourcesMeta = pikkuState(null, 'mcp', 'resourcesMeta')
|
|
59
54
|
const mcpResourceMeta = resourcesMeta[mcpResource.uri]
|
|
60
55
|
if (!mcpResourceMeta) {
|
|
61
|
-
|
|
62
|
-
`
|
|
56
|
+
console.warn(
|
|
57
|
+
`[pikku] Skipping MCP resource '${mcpResource.uri}' — metadata not found. Consider moving this wiring to its own file.`
|
|
63
58
|
)
|
|
59
|
+
return
|
|
64
60
|
}
|
|
65
61
|
addFunction(mcpResourceMeta.pikkuFuncId, mcpResource.func as any)
|
|
66
62
|
const resources = pikkuState(null, 'mcp', 'resources')
|
|
@@ -80,9 +76,10 @@ export const wireMCPPrompt = <
|
|
|
80
76
|
const promptsMeta = pikkuState(null, 'mcp', 'promptsMeta')
|
|
81
77
|
const mcpPromptMeta = promptsMeta[mcpPrompt.name]
|
|
82
78
|
if (!mcpPromptMeta) {
|
|
83
|
-
|
|
84
|
-
`
|
|
79
|
+
console.warn(
|
|
80
|
+
`[pikku] Skipping MCP prompt '${mcpPrompt.name}' — metadata not found. Consider moving this wiring to its own file.`
|
|
85
81
|
)
|
|
82
|
+
return
|
|
86
83
|
}
|
|
87
84
|
addFunction(mcpPromptMeta.pikkuFuncId, mcpPrompt.func as any)
|
|
88
85
|
const prompts = pikkuState(null, 'mcp', 'prompts')
|
|
@@ -72,7 +72,7 @@ describe('wireQueueWorker', () => {
|
|
|
72
72
|
assert.equal(registrations.get('test-queue'), mockWorker)
|
|
73
73
|
})
|
|
74
74
|
|
|
75
|
-
test('should
|
|
75
|
+
test('should skip when queue worker metadata not found', () => {
|
|
76
76
|
const mockWorker: CoreQueueWorker = {
|
|
77
77
|
name: 'missing-meta-queue',
|
|
78
78
|
func: {
|
|
@@ -81,16 +81,10 @@ describe('wireQueueWorker', () => {
|
|
|
81
81
|
},
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
error.message.includes('Missing generated metadata for queue worker')
|
|
89
|
-
)
|
|
90
|
-
assert(error.message.includes('missing-meta-queue'))
|
|
91
|
-
return true
|
|
92
|
-
}
|
|
93
|
-
)
|
|
84
|
+
wireQueueWorker(mockWorker)
|
|
85
|
+
|
|
86
|
+
const registrations = pikkuState(null, 'queue', 'registrations')
|
|
87
|
+
assert.equal(registrations.has('missing-meta-queue'), false)
|
|
94
88
|
})
|
|
95
89
|
|
|
96
90
|
test('should wire worker with middleware and tags', () => {
|
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
pikkuState,
|
|
13
13
|
} from '../../pikku-state.js'
|
|
14
14
|
import { addFunction, runPikkuFunc } from '../../function/function-runner.js'
|
|
15
|
-
|
|
16
15
|
/**
|
|
17
16
|
* Error class for queue processor not found
|
|
18
17
|
*/
|
|
@@ -59,9 +58,10 @@ export const wireQueueWorker = <
|
|
|
59
58
|
const meta = pikkuState(null, 'queue', 'meta')
|
|
60
59
|
const processorMeta = meta[queueWorker.name]
|
|
61
60
|
if (!processorMeta) {
|
|
62
|
-
|
|
63
|
-
`
|
|
61
|
+
console.warn(
|
|
62
|
+
`[pikku] Skipping queue worker '${queueWorker.name}' — metadata not found. Consider moving this wiring to its own file.`
|
|
64
63
|
)
|
|
64
|
+
return
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
// Register the function with pikku
|
|
@@ -105,13 +105,19 @@ export async function removeQueueWorker(name: string): Promise<void> {
|
|
|
105
105
|
export async function runQueueJob({
|
|
106
106
|
job,
|
|
107
107
|
updateProgress,
|
|
108
|
+
traceId,
|
|
108
109
|
}: {
|
|
109
110
|
job: QueueJob
|
|
110
111
|
updateProgress?: (progress: number | string | object) => Promise<void>
|
|
112
|
+
/** Pre-resolved trace ID (e.g. from queue message metadata) */
|
|
113
|
+
traceId?: string
|
|
111
114
|
}): Promise<void> {
|
|
112
115
|
const singletonServices = getSingletonServices()
|
|
113
116
|
const createWireServices = getCreateWireServices()
|
|
114
|
-
const
|
|
117
|
+
const resolvedTraceId = traceId ?? `q-${job.id}`
|
|
118
|
+
const logger =
|
|
119
|
+
singletonServices.logger.scope?.(resolvedTraceId) ??
|
|
120
|
+
singletonServices.logger
|
|
115
121
|
|
|
116
122
|
const meta = pikkuState(null, 'queue', 'meta')
|
|
117
123
|
const processorMeta = meta[job.queueName]
|
|
@@ -132,6 +138,7 @@ export async function runQueueJob({
|
|
|
132
138
|
const queue: PikkuQueue = {
|
|
133
139
|
queueName: job.queueName,
|
|
134
140
|
jobId: job.id,
|
|
141
|
+
pikkuUserId: job.pikkuUserId,
|
|
135
142
|
updateProgress:
|
|
136
143
|
updateProgress ||
|
|
137
144
|
(async (progress: number | string | object) => {
|
|
@@ -150,6 +157,7 @@ export async function runQueueJob({
|
|
|
150
157
|
logger.info(`Processing job ${job.id} in queue ${job.queueName}`)
|
|
151
158
|
|
|
152
159
|
const wire: PikkuWire = {
|
|
160
|
+
traceId: resolvedTraceId,
|
|
153
161
|
queue,
|
|
154
162
|
}
|
|
155
163
|
|
|
@@ -96,6 +96,8 @@ export interface QueueJob<T = any, R = any> {
|
|
|
96
96
|
result?: R
|
|
97
97
|
waitForCompletion?: (ttl?: number) => Promise<R>
|
|
98
98
|
metadata?: () => Promise<QueueJobMetadata> | QueueJobMetadata
|
|
99
|
+
/** Pikku user ID propagated from the job producer */
|
|
100
|
+
pikkuUserId?: string
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
/**
|
|
@@ -109,6 +111,8 @@ export interface JobOptions {
|
|
|
109
111
|
removeOnComplete?: number
|
|
110
112
|
removeOnFail?: number
|
|
111
113
|
jobId?: string
|
|
114
|
+
/** Pikku user ID to propagate to the queue worker for credential resolution */
|
|
115
|
+
pikkuUserId?: string
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
/**
|
|
@@ -180,6 +184,8 @@ export interface PikkuQueue {
|
|
|
180
184
|
queueName: string
|
|
181
185
|
/** The current job ID */
|
|
182
186
|
jobId: string
|
|
187
|
+
/** Pikku user ID propagated from the job producer for credential resolution */
|
|
188
|
+
pikkuUserId?: string
|
|
183
189
|
/** Update job progress (0-100 or custom value) */
|
|
184
190
|
updateProgress: (progress: number | string | object) => Promise<void>
|
|
185
191
|
/** Fail the current job with optional reason */
|
|
@@ -7,7 +7,6 @@ import { ForbiddenError } from '../../errors/errors.js'
|
|
|
7
7
|
import { PikkuError, addError } from '../../errors/error-handler.js'
|
|
8
8
|
import type { PikkuRPC, ResolvedFunction } from './rpc-types.js'
|
|
9
9
|
import { parseVersionedId } from '../../version.js'
|
|
10
|
-
import { encryptJSON } from '../../crypto-utils.js'
|
|
11
10
|
|
|
12
11
|
export class RPCNotFoundError extends PikkuError {
|
|
13
12
|
public readonly rpcName: string
|
|
@@ -118,23 +117,51 @@ export class ContextAwareRPCService {
|
|
|
118
117
|
: undefined,
|
|
119
118
|
}
|
|
120
119
|
|
|
121
|
-
// Check
|
|
120
|
+
// Check addon namespace first (e.g. 'stripe:createCharge')
|
|
122
121
|
if (funcName.includes(':')) {
|
|
123
|
-
|
|
122
|
+
try {
|
|
123
|
+
return await this.invokeAddonFunction<In, Out>(
|
|
124
|
+
funcName,
|
|
125
|
+
data,
|
|
126
|
+
updatedWire
|
|
127
|
+
)
|
|
128
|
+
} catch (addonErr) {
|
|
129
|
+
if (!(addonErr instanceof RPCNotFoundError)) throw addonErr
|
|
130
|
+
// Not an addon — fall through to local lookup
|
|
131
|
+
}
|
|
124
132
|
}
|
|
125
133
|
|
|
126
|
-
//
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
// Try local function, then fall back to deployment service (remote)
|
|
135
|
+
try {
|
|
136
|
+
return await runPikkuFunc<In, Out>(
|
|
137
|
+
'rpc',
|
|
138
|
+
funcName,
|
|
139
|
+
getPikkuFunctionName(funcName),
|
|
140
|
+
{
|
|
141
|
+
auth: this.options.requiresAuth,
|
|
142
|
+
singletonServices: this.services,
|
|
143
|
+
data: () => data,
|
|
144
|
+
wire: updatedWire,
|
|
145
|
+
}
|
|
146
|
+
)
|
|
147
|
+
} catch (e) {
|
|
148
|
+
if (e instanceof RPCNotFoundError) {
|
|
149
|
+
// Fall back to deployment service (e.g. CF service binding, Lambda Invoke)
|
|
150
|
+
if (this.services.deploymentService) {
|
|
151
|
+
const session =
|
|
152
|
+
this.wire.getSession && typeof this.wire.getSession === 'function'
|
|
153
|
+
? await this.wire.getSession()
|
|
154
|
+
: (this.wire as any).session
|
|
155
|
+
return this.services.deploymentService.invoke(
|
|
156
|
+
funcName,
|
|
157
|
+
data,
|
|
158
|
+
session,
|
|
159
|
+
this.wire.traceId
|
|
160
|
+
) as Promise<Out>
|
|
161
|
+
}
|
|
136
162
|
}
|
|
137
|
-
|
|
163
|
+
throw e
|
|
164
|
+
}
|
|
138
165
|
}
|
|
139
166
|
|
|
140
167
|
/**
|
|
@@ -151,10 +178,7 @@ export class ContextAwareRPCService {
|
|
|
151
178
|
// Resolve namespace to package name
|
|
152
179
|
const resolved = resolveNamespace(namespacedFunction)
|
|
153
180
|
if (!resolved) {
|
|
154
|
-
throw new
|
|
155
|
-
`Unknown namespace in function reference: ${namespacedFunction}. ` +
|
|
156
|
-
`Make sure the package is registered in addons config.`
|
|
157
|
-
)
|
|
181
|
+
throw new RPCNotFoundError(namespacedFunction)
|
|
158
182
|
}
|
|
159
183
|
|
|
160
184
|
// Get the function meta from the addon package
|
|
@@ -162,10 +186,7 @@ export class ContextAwareRPCService {
|
|
|
162
186
|
const addonFunctionMeta = pikkuState(resolved.package, 'function', 'meta')
|
|
163
187
|
const funcMeta = addonFunctionMeta[resolved.function]
|
|
164
188
|
if (!funcMeta) {
|
|
165
|
-
throw new
|
|
166
|
-
`Function '${resolved.function}' not found in package '${resolved.package}'. ` +
|
|
167
|
-
`Available functions: ${Object.keys(addonFunctionMeta).join(', ') || '(none)'}`
|
|
168
|
-
)
|
|
189
|
+
throw new RPCNotFoundError(namespacedFunction)
|
|
169
190
|
}
|
|
170
191
|
const funcName = funcMeta.pikkuFuncId || resolved.function
|
|
171
192
|
|
|
@@ -210,17 +231,33 @@ export class ContextAwareRPCService {
|
|
|
210
231
|
return this.invokeAddonFunction<In, Out>(rpcName, data, mergedWire)
|
|
211
232
|
}
|
|
212
233
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
234
|
+
try {
|
|
235
|
+
return await runPikkuFunc<In, Out>(
|
|
236
|
+
'rpc',
|
|
237
|
+
rpcName,
|
|
238
|
+
getPikkuFunctionName(rpcName),
|
|
239
|
+
{
|
|
240
|
+
auth: this.options.requiresAuth,
|
|
241
|
+
singletonServices: this.services,
|
|
242
|
+
data: () => data,
|
|
243
|
+
wire: mergedWire,
|
|
244
|
+
}
|
|
245
|
+
)
|
|
246
|
+
} catch (e) {
|
|
247
|
+
if (e instanceof RPCNotFoundError && this.services.deploymentService) {
|
|
248
|
+
const session =
|
|
249
|
+
this.wire.getSession && typeof this.wire.getSession === 'function'
|
|
250
|
+
? await this.wire.getSession()
|
|
251
|
+
: (this.wire as any).session
|
|
252
|
+
return this.services.deploymentService.invoke(
|
|
253
|
+
rpcName,
|
|
254
|
+
data,
|
|
255
|
+
session,
|
|
256
|
+
this.wire.traceId
|
|
257
|
+
) as Promise<Out>
|
|
222
258
|
}
|
|
223
|
-
|
|
259
|
+
throw e
|
|
260
|
+
}
|
|
224
261
|
}
|
|
225
262
|
|
|
226
263
|
public async startWorkflow<In = any>(
|
|
@@ -239,6 +276,7 @@ export class ContextAwareRPCService {
|
|
|
239
276
|
type: this.wire.wireType ?? 'unknown',
|
|
240
277
|
id: this.wire.wireId,
|
|
241
278
|
...(parentRunId ? { parentRunId } : {}),
|
|
279
|
+
...(this.wire.pikkuUserId ? { pikkuUserId: this.wire.pikkuUserId } : {}),
|
|
242
280
|
}
|
|
243
281
|
return this.services.workflowService.startWorkflow(
|
|
244
282
|
workflowName,
|
|
@@ -306,9 +344,14 @@ export class ContextAwareRPCService {
|
|
|
306
344
|
approvals: { toolCallId: string; approved: boolean }[],
|
|
307
345
|
expectedAgentName?: string
|
|
308
346
|
) => {
|
|
309
|
-
const result = await resumeAIAgentSync(
|
|
310
|
-
|
|
311
|
-
|
|
347
|
+
const result = await resumeAIAgentSync(
|
|
348
|
+
runId,
|
|
349
|
+
approvals,
|
|
350
|
+
{
|
|
351
|
+
sessionService: this.options.sessionService,
|
|
352
|
+
},
|
|
353
|
+
expectedAgentName
|
|
354
|
+
)
|
|
312
355
|
return {
|
|
313
356
|
runId: result.runId,
|
|
314
357
|
result: result.object ?? result.text,
|
|
@@ -326,94 +369,24 @@ export class ContextAwareRPCService {
|
|
|
326
369
|
funcName: string,
|
|
327
370
|
data: In
|
|
328
371
|
): Promise<Out> {
|
|
329
|
-
|
|
330
|
-
const headers: Record<string, string> = {
|
|
331
|
-
'Content-Type': 'application/json',
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
const colonIndex = funcName.indexOf(':')
|
|
335
|
-
if (colonIndex !== -1) {
|
|
336
|
-
const namespace = funcName.substring(0, colonIndex)
|
|
337
|
-
const addons = pikkuState(null, 'addons', 'packages')
|
|
338
|
-
const pkgConfig = addons.get(namespace)
|
|
339
|
-
endpoint = pkgConfig?.rpcEndpoint
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
if (!endpoint && this.services.deploymentService) {
|
|
343
|
-
const deployments =
|
|
344
|
-
await this.services.deploymentService.findFunction(funcName)
|
|
345
|
-
if (deployments.length > 0) {
|
|
346
|
-
endpoint = deployments[0].endpoint
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
if (!endpoint) {
|
|
372
|
+
if (!this.services.deploymentService) {
|
|
351
373
|
throw new Error(
|
|
352
|
-
`No
|
|
353
|
-
`
|
|
374
|
+
`No DeploymentService configured for remote RPC: ${funcName}. ` +
|
|
375
|
+
`Set up a DeploymentService to enable remote function calls.`
|
|
354
376
|
)
|
|
355
377
|
}
|
|
356
378
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
} catch {}
|
|
362
|
-
}
|
|
363
|
-
if (secret) {
|
|
364
|
-
if (!this.services.jwt) {
|
|
365
|
-
throw new Error(
|
|
366
|
-
'PIKKU_REMOTE_SECRET is set but JWT service is not available'
|
|
367
|
-
)
|
|
368
|
-
}
|
|
369
|
-
const session =
|
|
370
|
-
this.wire.getSession && typeof this.wire.getSession === 'function'
|
|
371
|
-
? await this.wire.getSession()
|
|
372
|
-
: (this.wire as any).session
|
|
373
|
-
const sessionEnc = session
|
|
374
|
-
? await encryptJSON(secret, { session })
|
|
375
|
-
: undefined
|
|
376
|
-
const token = await this.services.jwt.encode(
|
|
377
|
-
{ value: 5, unit: 'minute' },
|
|
378
|
-
{
|
|
379
|
-
aud: 'pikku-remote',
|
|
380
|
-
fn: funcName,
|
|
381
|
-
iat: Date.now(),
|
|
382
|
-
session: sessionEnc,
|
|
383
|
-
}
|
|
384
|
-
)
|
|
385
|
-
headers.Authorization = `Bearer ${token}`
|
|
386
|
-
}
|
|
379
|
+
const session =
|
|
380
|
+
this.wire.getSession && typeof this.wire.getSession === 'function'
|
|
381
|
+
? await this.wire.getSession()
|
|
382
|
+
: (this.wire as any).session
|
|
387
383
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
395
|
-
})
|
|
396
|
-
|
|
397
|
-
if (!response.ok) {
|
|
398
|
-
let errorBody: string
|
|
399
|
-
try {
|
|
400
|
-
errorBody = JSON.stringify(await response.json())
|
|
401
|
-
} catch {
|
|
402
|
-
errorBody = await response.text()
|
|
403
|
-
}
|
|
404
|
-
throw new Error(
|
|
405
|
-
`Remote RPC call failed: ${response.status} ${response.statusText}. ${errorBody}`
|
|
406
|
-
)
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
try {
|
|
410
|
-
return (await response.json()) as Out
|
|
411
|
-
} catch {
|
|
412
|
-
const text = await response.text()
|
|
413
|
-
throw new Error(
|
|
414
|
-
`Remote RPC call returned non-JSON response: ${response.status} ${response.statusText}. ${text}`
|
|
415
|
-
)
|
|
416
|
-
}
|
|
384
|
+
return this.services.deploymentService.invoke(
|
|
385
|
+
funcName,
|
|
386
|
+
data,
|
|
387
|
+
session,
|
|
388
|
+
this.wire.traceId
|
|
389
|
+
) as Promise<Out>
|
|
417
390
|
}
|
|
418
391
|
}
|
|
419
392
|
|