@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
package/src/index.ts
CHANGED
|
@@ -98,7 +98,16 @@ export type { QueueService } from './wirings/queue/queue.types.js'
|
|
|
98
98
|
export type { JWTService } from './services/jwt-service.js'
|
|
99
99
|
export type { SecretService } from './services/secret-service.js'
|
|
100
100
|
export type { VariablesService } from './services/variables-service.js'
|
|
101
|
-
export type {
|
|
101
|
+
export type {
|
|
102
|
+
ContentService,
|
|
103
|
+
SignContentKeyArgs,
|
|
104
|
+
SignURLArgs,
|
|
105
|
+
GetUploadURLArgs,
|
|
106
|
+
UploadURLResult,
|
|
107
|
+
BucketKeyArgs,
|
|
108
|
+
WriteFileArgs,
|
|
109
|
+
CopyFileArgs,
|
|
110
|
+
} from './services/content-service.js'
|
|
102
111
|
export type { DeploymentService } from './services/deployment-service.js'
|
|
103
112
|
export type { WorkflowService } from './services/workflow-service.js'
|
|
104
113
|
export type { GatewayService } from './services/gateway-service.js'
|
|
@@ -114,8 +123,18 @@ export { createGraph } from './wirings/workflow/graph/graph-node.js'
|
|
|
114
123
|
export { wireAddon } from './wirings/rpc/wire-addon.js'
|
|
115
124
|
export type { WireAddonConfig } from './wirings/rpc/wire-addon.js'
|
|
116
125
|
export type { PikkuPackageState } from './types/state.types.js'
|
|
117
|
-
export {
|
|
118
|
-
|
|
126
|
+
export {
|
|
127
|
+
runMiddleware,
|
|
128
|
+
addTagMiddleware,
|
|
129
|
+
addTagMiddleware as addMiddleware,
|
|
130
|
+
addGlobalMiddleware,
|
|
131
|
+
} from './middleware-runner.js'
|
|
132
|
+
export {
|
|
133
|
+
addTagPermission,
|
|
134
|
+
addTagPermission as addPermission,
|
|
135
|
+
addGlobalPermission,
|
|
136
|
+
checkAuthPermissions,
|
|
137
|
+
} from './permissions.js'
|
|
119
138
|
export { isSerializable, stopSingletonServices } from './utils.js'
|
|
120
139
|
export { getSingletonServices, getCreateWireServices } from './pikku-state.js'
|
|
121
140
|
export {
|
package/src/middleware/index.ts
CHANGED
|
@@ -4,5 +4,14 @@ export { authBearer } from './auth-bearer.js'
|
|
|
4
4
|
export { pikkuRemoteAuthMiddleware } from './remote-auth.js'
|
|
5
5
|
export { cors } from './cors.js'
|
|
6
6
|
export { telemetryOuter, telemetryInner } from './telemetry.js'
|
|
7
|
-
export {
|
|
8
|
-
|
|
7
|
+
export {
|
|
8
|
+
addTagMiddleware,
|
|
9
|
+
addTagMiddleware as addMiddleware,
|
|
10
|
+
addGlobalMiddleware,
|
|
11
|
+
runMiddleware,
|
|
12
|
+
} from '../middleware-runner.js'
|
|
13
|
+
export {
|
|
14
|
+
addTagPermission,
|
|
15
|
+
addTagPermission as addPermission,
|
|
16
|
+
addGlobalPermission,
|
|
17
|
+
} from '../permissions.js'
|
|
@@ -13,7 +13,7 @@ import { pikkuMiddleware, pikkuMiddlewareFactory } from '../types/core.types.js'
|
|
|
13
13
|
* @example
|
|
14
14
|
* ```typescript
|
|
15
15
|
* import { telemetryOuter } from '@pikku/core/middleware'
|
|
16
|
-
*
|
|
16
|
+
* addTagMiddleware('myTag', [telemetryOuter()])
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
19
|
export const telemetryOuter = pikkuMiddlewareFactory<{
|
|
@@ -70,7 +70,7 @@ export const telemetryOuter = pikkuMiddlewareFactory<{
|
|
|
70
70
|
* @example
|
|
71
71
|
* ```typescript
|
|
72
72
|
* import { telemetryInner } from '@pikku/core/middleware'
|
|
73
|
-
*
|
|
73
|
+
* addTagMiddleware('myTag', [telemetryInner()])
|
|
74
74
|
* ```
|
|
75
75
|
*/
|
|
76
76
|
export const telemetryInner = pikkuMiddlewareFactory<{
|
|
@@ -2,7 +2,7 @@ import { describe, test, beforeEach } from 'node:test'
|
|
|
2
2
|
import assert from 'node:assert'
|
|
3
3
|
import {
|
|
4
4
|
combineMiddleware,
|
|
5
|
-
|
|
5
|
+
addTagMiddleware,
|
|
6
6
|
runMiddleware,
|
|
7
7
|
} from './middleware-runner.js'
|
|
8
8
|
import { resetPikkuState } from './pikku-state.js'
|
|
@@ -115,8 +115,8 @@ describe('combineMiddleware', () => {
|
|
|
115
115
|
await next()
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
addTagMiddleware('wiringTag', [wiringTagMiddleware])
|
|
119
|
+
addTagMiddleware('funcTag', [funcTagMiddleware])
|
|
120
120
|
|
|
121
121
|
const wiringMiddleware: CorePikkuMiddleware = async (
|
|
122
122
|
services,
|
|
@@ -157,7 +157,7 @@ describe('combineMiddleware', () => {
|
|
|
157
157
|
await next()
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
|
|
160
|
+
addTagMiddleware('testTag', [taggedMiddleware])
|
|
161
161
|
|
|
162
162
|
const result = combineMiddleware('http', Math.random().toString(), {
|
|
163
163
|
wireInheritedMiddleware: [{ type: 'tag', tag: 'testTag' }],
|
|
@@ -176,7 +176,7 @@ describe('combineMiddleware', () => {
|
|
|
176
176
|
await next()
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
|
|
179
|
+
addTagMiddleware('funcTestTag', [taggedMiddleware])
|
|
180
180
|
|
|
181
181
|
const result = combineMiddleware('http', Math.random().toString(), {
|
|
182
182
|
funcInheritedMiddleware: [{ type: 'tag', tag: 'funcTestTag' }],
|
|
@@ -194,8 +194,8 @@ describe('combineMiddleware', () => {
|
|
|
194
194
|
await next()
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
addTagMiddleware('tag1', [middleware1])
|
|
198
|
+
addTagMiddleware('tag2', [middleware2])
|
|
199
199
|
|
|
200
200
|
const result = combineMiddleware('http', Math.random().toString(), {
|
|
201
201
|
wireInheritedMiddleware: [
|
|
@@ -218,7 +218,7 @@ describe('combineMiddleware', () => {
|
|
|
218
218
|
await next()
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
|
|
221
|
+
addTagMiddleware('billing', [billingMiddleware])
|
|
222
222
|
|
|
223
223
|
const result = combineMiddleware('http', Math.random().toString(), {
|
|
224
224
|
wireInheritedMiddleware: [{ type: 'tag', tag: 'billing:read' }],
|
|
@@ -237,7 +237,7 @@ describe('combineMiddleware', () => {
|
|
|
237
237
|
await next()
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
-
|
|
240
|
+
addTagMiddleware('billing', [billingMiddleware])
|
|
241
241
|
|
|
242
242
|
const result = combineMiddleware('http', Math.random().toString(), {
|
|
243
243
|
wireInheritedMiddleware: [{ type: 'tag', tag: 'billing' }],
|
|
@@ -263,8 +263,8 @@ describe('combineMiddleware', () => {
|
|
|
263
263
|
await next()
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
-
|
|
267
|
-
|
|
266
|
+
addTagMiddleware('billing', [billingMiddleware])
|
|
267
|
+
addTagMiddleware('billing:read', [billingReadMiddleware])
|
|
268
268
|
|
|
269
269
|
const result = combineMiddleware('http', Math.random().toString(), {
|
|
270
270
|
wireInheritedMiddleware: [{ type: 'tag', tag: 'billing:read' }],
|
|
@@ -298,9 +298,9 @@ describe('combineMiddleware', () => {
|
|
|
298
298
|
await next()
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
301
|
+
addTagMiddleware('billing', [billingMiddleware])
|
|
302
|
+
addTagMiddleware('billing:read', [billingReadMiddleware])
|
|
303
|
+
addTagMiddleware('billing:read:admin', [billingReadAdminMiddleware])
|
|
304
304
|
|
|
305
305
|
const result = combineMiddleware('http', Math.random().toString(), {
|
|
306
306
|
wireInheritedMiddleware: [{ type: 'tag', tag: 'billing:read:admin' }],
|
|
@@ -321,7 +321,7 @@ describe('combineMiddleware', () => {
|
|
|
321
321
|
await next()
|
|
322
322
|
}
|
|
323
323
|
|
|
324
|
-
|
|
324
|
+
addTagMiddleware('billing', [billingMiddleware])
|
|
325
325
|
|
|
326
326
|
const result = combineMiddleware('http', Math.random().toString(), {
|
|
327
327
|
funcInheritedMiddleware: [{ type: 'tag', tag: 'billing:write' }],
|
|
@@ -336,7 +336,7 @@ describe('combineMiddleware', () => {
|
|
|
336
336
|
await next()
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
-
|
|
339
|
+
addTagMiddleware('existingTag', [middleware])
|
|
340
340
|
|
|
341
341
|
const result = combineMiddleware('http', Math.random().toString(), {
|
|
342
342
|
wireInheritedMiddleware: [
|
package/src/middleware-runner.ts
CHANGED
|
@@ -91,39 +91,16 @@ const isSortedByPriority = (
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
/**
|
|
94
|
-
* Registers
|
|
94
|
+
* Registers tag-scoped middleware. Applies to any wiring (HTTP, Channel,
|
|
95
|
+
* Queue, Scheduler, MCP, CLI, Workflow, Agent) that carries the matching tag.
|
|
95
96
|
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* For tree-shaking benefits, wrap in a factory function:
|
|
100
|
-
* `export const x = () => addMiddleware('tag', [...])`
|
|
101
|
-
*
|
|
102
|
-
* Accepts an array that can contain:
|
|
103
|
-
* - Direct middleware functions (CorePikkuMiddleware)
|
|
104
|
-
* - Factory middleware functions (CorePikkuMiddlewareFactory)
|
|
105
|
-
*
|
|
106
|
-
* @template PikkuMiddleware The middleware type.
|
|
107
|
-
* @param {string} tag - The tag that the middleware should apply to.
|
|
108
|
-
* @param {CorePikkuMiddlewareGroup} middleware - Array of middleware for this tag.
|
|
109
|
-
*
|
|
110
|
-
* @returns {CorePikkuMiddlewareGroup} The middleware array (for chaining/wrapping).
|
|
97
|
+
* For tree-shaking, wrap in a factory:
|
|
98
|
+
* `export const x = () => addTagMiddleware('tag', [...])`
|
|
111
99
|
*
|
|
112
100
|
* @example
|
|
113
|
-
*
|
|
114
|
-
* // Recommended: tree-shakeable
|
|
115
|
-
* export const adminMiddleware = () => addMiddleware('admin', [
|
|
116
|
-
* authMiddleware,
|
|
117
|
-
* loggingMiddleware({ level: 'info' })
|
|
118
|
-
* ])
|
|
119
|
-
*
|
|
120
|
-
* // Also works: no tree-shaking
|
|
121
|
-
* export const apiMiddleware = addMiddleware('api', [
|
|
122
|
-
* rateLimitMiddleware
|
|
123
|
-
* ])
|
|
124
|
-
* ```
|
|
101
|
+
* export const adminMiddleware = () => addTagMiddleware('admin', [authMiddleware])
|
|
125
102
|
*/
|
|
126
|
-
export const
|
|
103
|
+
export const addTagMiddleware = <PikkuMiddleware extends CorePikkuMiddleware>(
|
|
127
104
|
tag: string,
|
|
128
105
|
middleware: CorePikkuMiddlewareGroup,
|
|
129
106
|
packageName: string | null = null
|
|
@@ -133,6 +110,33 @@ export const addMiddleware = <PikkuMiddleware extends CorePikkuMiddleware>(
|
|
|
133
110
|
return middleware
|
|
134
111
|
}
|
|
135
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Registers wire-agnostic global middleware. Runs at the top of every
|
|
115
|
+
* wiring's middleware chain — before wire-, tag-, and function-level
|
|
116
|
+
* entries — across HTTP, Channel, Queue, Scheduler, MCP, CLI, Workflow, Agent.
|
|
117
|
+
*
|
|
118
|
+
* Resolution order: global → wire → tag → function.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* addGlobalMiddleware([telemetryMiddleware])
|
|
122
|
+
*/
|
|
123
|
+
export const addGlobalMiddleware = <
|
|
124
|
+
PikkuMiddleware extends CorePikkuMiddleware,
|
|
125
|
+
>(
|
|
126
|
+
middleware: CorePikkuMiddlewareGroup,
|
|
127
|
+
packageName: string | null = null
|
|
128
|
+
): CorePikkuMiddlewareGroup => {
|
|
129
|
+
const state = pikkuState(
|
|
130
|
+
packageName,
|
|
131
|
+
'middleware',
|
|
132
|
+
'global'
|
|
133
|
+
) as unknown as CorePikkuMiddlewareGroup
|
|
134
|
+
;(state as CorePikkuMiddleware[]).push(
|
|
135
|
+
...(middleware as CorePikkuMiddleware[])
|
|
136
|
+
)
|
|
137
|
+
return middleware
|
|
138
|
+
}
|
|
139
|
+
|
|
136
140
|
/**
|
|
137
141
|
* Retrieves a registered middleware function by its name.
|
|
138
142
|
*
|
|
@@ -218,7 +222,15 @@ export const combineMiddleware = (
|
|
|
218
222
|
|
|
219
223
|
const resolved: CorePikkuMiddleware[] = []
|
|
220
224
|
|
|
221
|
-
|
|
225
|
+
const globals = pikkuState(
|
|
226
|
+
packageName,
|
|
227
|
+
'middleware',
|
|
228
|
+
'global'
|
|
229
|
+
) as unknown as CorePikkuMiddleware[]
|
|
230
|
+
if (globals && globals.length > 0) {
|
|
231
|
+
resolved.push(...globals)
|
|
232
|
+
}
|
|
233
|
+
|
|
222
234
|
if (wireInheritedMiddleware) {
|
|
223
235
|
for (const meta of wireInheritedMiddleware) {
|
|
224
236
|
if (meta.type === 'http') {
|
package/src/permissions.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, test, beforeEach } from 'node:test'
|
|
2
2
|
import * as assert from 'node:assert'
|
|
3
|
-
import {
|
|
3
|
+
import { addTagPermission, runPermissions } from './permissions.js'
|
|
4
4
|
import { pikkuState, resetPikkuState } from './pikku-state.js'
|
|
5
5
|
import type { CoreServices, CoreUserSession } from './types/core.types.js'
|
|
6
6
|
import type {
|
|
@@ -47,11 +47,11 @@ const mockSession: CoreUserSession = {
|
|
|
47
47
|
userId: 'test-user',
|
|
48
48
|
} as any
|
|
49
49
|
|
|
50
|
-
describe('
|
|
50
|
+
describe('addTagPermission', () => {
|
|
51
51
|
test('should add single permission for a tag', () => {
|
|
52
52
|
const mockPermission = async () => true
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
addTagPermission('testTag', [mockPermission])
|
|
55
55
|
|
|
56
56
|
const permissions = getPermissionsForTags(['testTag'])
|
|
57
57
|
assert.equal(permissions.length, 1)
|
|
@@ -62,7 +62,7 @@ describe('addPermission', () => {
|
|
|
62
62
|
const mockPermission1 = async () => true
|
|
63
63
|
const mockPermission2 = async () => false
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
addTagPermission('multiTestTag', [mockPermission1, mockPermission2])
|
|
66
66
|
|
|
67
67
|
const permissions = getPermissionsForTags(['multiTestTag'])
|
|
68
68
|
assert.equal(permissions.length, 2)
|
|
@@ -75,7 +75,7 @@ describe('addPermission', () => {
|
|
|
75
75
|
permissions: [async () => true, async () => false],
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
addTagPermission('groupTestTag', mockPermissionGroup)
|
|
79
79
|
|
|
80
80
|
const permissions = getPermissionsForTags(['groupTestTag'])
|
|
81
81
|
assert.equal(permissions.length, 1)
|
|
@@ -85,7 +85,7 @@ describe('addPermission', () => {
|
|
|
85
85
|
test('should apply parent tag permission to namespaced tag via runPermissions', async () => {
|
|
86
86
|
const billingPermission = async () => true
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
addTagPermission('billing', [billingPermission])
|
|
89
89
|
|
|
90
90
|
await runPermissions('rpc', Math.random().toString(), {
|
|
91
91
|
wireInheritedPermissions: [{ type: 'tag', tag: 'billing:write' }],
|
|
@@ -106,8 +106,8 @@ describe('addPermission', () => {
|
|
|
106
106
|
return true
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
addTagPermission('billing', [billingPermission])
|
|
110
|
+
addTagPermission('billing:read', [billingReadPermission])
|
|
111
111
|
|
|
112
112
|
await runPermissions('rpc', Math.random().toString(), {
|
|
113
113
|
wireInheritedPermissions: [{ type: 'tag', tag: 'billing:read' }],
|
|
@@ -134,9 +134,9 @@ describe('addPermission', () => {
|
|
|
134
134
|
return true
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
addTagPermission('billing', [billingPermission])
|
|
138
|
+
addTagPermission('billing:read', [billingReadPermission])
|
|
139
|
+
addTagPermission('billing:read:admin', [billingReadAdminPermission])
|
|
140
140
|
|
|
141
141
|
await runPermissions('rpc', Math.random().toString(), {
|
|
142
142
|
wireInheritedPermissions: [{ type: 'tag', tag: 'billing:read:admin' }],
|
|
@@ -151,7 +151,7 @@ describe('addPermission', () => {
|
|
|
151
151
|
test('should apply parent tag permission via funcInheritedPermissions', async () => {
|
|
152
152
|
const billingPermission = async () => true
|
|
153
153
|
|
|
154
|
-
|
|
154
|
+
addTagPermission('billing', [billingPermission])
|
|
155
155
|
|
|
156
156
|
await runPermissions('rpc', Math.random().toString(), {
|
|
157
157
|
funcInheritedPermissions: [{ type: 'tag', tag: 'billing:delete' }],
|
|
@@ -164,11 +164,11 @@ describe('addPermission', () => {
|
|
|
164
164
|
test('should throw error when tag already exists', () => {
|
|
165
165
|
const mockPermission = async () => true
|
|
166
166
|
|
|
167
|
-
|
|
167
|
+
addTagPermission('duplicateTag', [mockPermission])
|
|
168
168
|
|
|
169
169
|
assert.throws(
|
|
170
170
|
() => {
|
|
171
|
-
|
|
171
|
+
addTagPermission('duplicateTag', [mockPermission])
|
|
172
172
|
},
|
|
173
173
|
{
|
|
174
174
|
message:
|
|
@@ -195,7 +195,7 @@ describe('runPermissions', () => {
|
|
|
195
195
|
return true
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
|
|
198
|
+
addTagPermission('wiringTag', [wiringTagPermission])
|
|
199
199
|
|
|
200
200
|
await runPermissions('rpc', Math.random().toString(), {
|
|
201
201
|
wireInheritedPermissions: [{ type: 'tag', tag: 'wiringTag' }],
|
|
@@ -219,8 +219,8 @@ describe('runPermissions', () => {
|
|
|
219
219
|
return true
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
|
|
223
|
-
|
|
222
|
+
addTagPermission('wiringTag', [wiringTagPermission])
|
|
223
|
+
addTagPermission('funcTag', [funcTagPermission])
|
|
224
224
|
|
|
225
225
|
const wirePermissions: CorePermissionGroup = {
|
|
226
226
|
permissions: [
|
|
@@ -258,7 +258,10 @@ describe('runPermissions', () => {
|
|
|
258
258
|
const failingPermission = async () => false
|
|
259
259
|
const passingPermission = async () => true
|
|
260
260
|
|
|
261
|
-
|
|
261
|
+
addTagPermission('atLeastOneTestTag', [
|
|
262
|
+
failingPermission,
|
|
263
|
+
passingPermission,
|
|
264
|
+
])
|
|
262
265
|
|
|
263
266
|
// Should not throw because at least one permission passes
|
|
264
267
|
await runPermissions('rpc', Math.random().toString(), {
|
|
@@ -273,7 +276,7 @@ describe('runPermissions', () => {
|
|
|
273
276
|
const failingPermission1 = async () => false
|
|
274
277
|
const failingPermission2 = async () => false
|
|
275
278
|
|
|
276
|
-
|
|
279
|
+
addTagPermission('allFailTestTag', [failingPermission1, failingPermission2])
|
|
277
280
|
|
|
278
281
|
await assert.rejects(
|
|
279
282
|
runPermissions('rpc', Math.random().toString(), {
|
|
@@ -309,7 +312,7 @@ describe('runPermissions', () => {
|
|
|
309
312
|
test('should throw error when all function tag permissions fail', async () => {
|
|
310
313
|
const failingPermission = async () => false
|
|
311
314
|
|
|
312
|
-
|
|
315
|
+
addTagPermission('funcTag', [failingPermission])
|
|
313
316
|
|
|
314
317
|
await assert.rejects(
|
|
315
318
|
runPermissions('rpc', Math.random().toString(), {
|
|
@@ -350,7 +353,7 @@ describe('runPermissions', () => {
|
|
|
350
353
|
return false
|
|
351
354
|
}
|
|
352
355
|
|
|
353
|
-
|
|
356
|
+
addTagPermission('failingWiringTag', [failingWiringTagPermission])
|
|
354
357
|
|
|
355
358
|
const wirePermissions: CorePermissionGroup = {
|
|
356
359
|
permissions: [
|
|
@@ -377,7 +380,7 @@ describe('runPermissions', () => {
|
|
|
377
380
|
test('should handle array permissions in tag-based permissions', async () => {
|
|
378
381
|
const arrayPermission = [async () => true, async () => false]
|
|
379
382
|
|
|
380
|
-
|
|
383
|
+
addTagPermission('arrayTestTag', arrayPermission)
|
|
381
384
|
|
|
382
385
|
// Should not throw because verifyPermissions handles array properly
|
|
383
386
|
await runPermissions('rpc', Math.random().toString(), {
|
|
@@ -393,7 +396,7 @@ describe('runPermissions', () => {
|
|
|
393
396
|
permissions: [async () => true],
|
|
394
397
|
}
|
|
395
398
|
|
|
396
|
-
|
|
399
|
+
addTagPermission('objectTestTag', permissionGroup)
|
|
397
400
|
|
|
398
401
|
// Should not throw because verifyPermissions handles objects properly
|
|
399
402
|
await runPermissions('rpc', Math.random().toString(), {
|
|
@@ -418,7 +421,7 @@ describe('runPermissions', () => {
|
|
|
418
421
|
|
|
419
422
|
const testData = { test: 'data' }
|
|
420
423
|
|
|
421
|
-
|
|
424
|
+
addTagPermission('paramTestTag', [testPermission])
|
|
422
425
|
|
|
423
426
|
await runPermissions('rpc', Math.random().toString(), {
|
|
424
427
|
wireInheritedPermissions: [{ type: 'tag', tag: 'paramTestTag' }],
|
package/src/permissions.ts
CHANGED
|
@@ -75,34 +75,16 @@ const getPermissionByName = (name: string): CorePikkuPermission | undefined => {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
|
-
*
|
|
78
|
+
* Registers tag-scoped permissions. Applies to any wiring that carries the
|
|
79
|
+
* matching tag.
|
|
79
80
|
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
* For tree-shaking benefits, wrap in a factory function:
|
|
84
|
-
* `export const x = () => addPermission('tag', [...])`
|
|
85
|
-
*
|
|
86
|
-
* @param {string} tag - The tag that the permissions should apply to.
|
|
87
|
-
* @param {any[]} permissions - The permissions array to apply for the specified tag.
|
|
88
|
-
*
|
|
89
|
-
* @returns {CorePermissionGroup | CorePikkuPermission[]} The permissions (for chaining/wrapping).
|
|
81
|
+
* For tree-shaking, wrap in a factory:
|
|
82
|
+
* `export const x = () => addTagPermission('tag', [...])`
|
|
90
83
|
*
|
|
91
84
|
* @example
|
|
92
|
-
*
|
|
93
|
-
* // Recommended: tree-shakeable
|
|
94
|
-
* export const adminPermissions = () => addPermission('admin', [
|
|
95
|
-
* adminPermission,
|
|
96
|
-
* rolePermission({ role: 'admin' })
|
|
97
|
-
* ])
|
|
98
|
-
*
|
|
99
|
-
* // Also works: no tree-shaking
|
|
100
|
-
* export const apiPermissions = addPermission('api', [
|
|
101
|
-
* readPermission
|
|
102
|
-
* ])
|
|
103
|
-
* ```
|
|
85
|
+
* export const adminPermissions = () => addTagPermission('admin', [adminPermission])
|
|
104
86
|
*/
|
|
105
|
-
export const
|
|
87
|
+
export const addTagPermission = (
|
|
106
88
|
tag: string,
|
|
107
89
|
permissions: CorePermissionGroup | CorePikkuPermission[],
|
|
108
90
|
packageName: string | null = null
|
|
@@ -117,6 +99,32 @@ export const addPermission = (
|
|
|
117
99
|
return permissions
|
|
118
100
|
}
|
|
119
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Registers wire-agnostic global permissions. Runs at the top of every
|
|
104
|
+
* wiring's permission resolution — before wire-, tag-, and function-level
|
|
105
|
+
* entries — across all wire types.
|
|
106
|
+
*
|
|
107
|
+
* Resolution order: global → wire → tag → function.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* addGlobalPermission([signedInUser])
|
|
111
|
+
*/
|
|
112
|
+
export const addGlobalPermission = (
|
|
113
|
+
permissions: CorePermissionGroup | CorePikkuPermission[],
|
|
114
|
+
packageName: string | null = null
|
|
115
|
+
): CorePermissionGroup | CorePikkuPermission[] => {
|
|
116
|
+
const state = pikkuState(packageName, 'permissions', 'global') as unknown as (
|
|
117
|
+
| CorePermissionGroup
|
|
118
|
+
| CorePikkuPermission
|
|
119
|
+
)[]
|
|
120
|
+
if (Array.isArray(permissions)) {
|
|
121
|
+
state.push(...(permissions as CorePikkuPermission[]))
|
|
122
|
+
} else {
|
|
123
|
+
state.push(permissions)
|
|
124
|
+
}
|
|
125
|
+
return permissions
|
|
126
|
+
}
|
|
127
|
+
|
|
120
128
|
const combinedPermissionsCache: Record<
|
|
121
129
|
PikkuWiringTypes,
|
|
122
130
|
Record<string, readonly (CorePermissionGroup | CorePikkuPermission)[]>
|
|
@@ -189,7 +197,15 @@ const combinePermissions = (
|
|
|
189
197
|
|
|
190
198
|
const resolved: (CorePermissionGroup | CorePikkuPermission)[] = []
|
|
191
199
|
|
|
192
|
-
|
|
200
|
+
const globals = pikkuState(
|
|
201
|
+
packageName,
|
|
202
|
+
'permissions',
|
|
203
|
+
'global'
|
|
204
|
+
) as unknown as (CorePermissionGroup | CorePikkuPermission)[]
|
|
205
|
+
if (globals && globals.length > 0) {
|
|
206
|
+
resolved.push(...globals)
|
|
207
|
+
}
|
|
208
|
+
|
|
193
209
|
if (wireInheritedPermissions) {
|
|
194
210
|
for (const meta of wireInheritedPermissions) {
|
|
195
211
|
if (meta.type === 'http') {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, test } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
|
|
4
|
+
import { PikkuRequest } from './pikku-request.js'
|
|
5
|
+
|
|
6
|
+
class TestPikkuRequest<In> extends PikkuRequest<In> {}
|
|
7
|
+
|
|
8
|
+
describe('PikkuRequest', () => {
|
|
9
|
+
test('returns stored data', async () => {
|
|
10
|
+
const request = new TestPikkuRequest({ hello: 'world' })
|
|
11
|
+
|
|
12
|
+
assert.deepEqual(await request.data(), { hello: 'world' })
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test('preserves falsey payloads', async () => {
|
|
16
|
+
await assert.doesNotReject(async () => {
|
|
17
|
+
const zeroRequest = new TestPikkuRequest(0)
|
|
18
|
+
assert.equal(await zeroRequest.data(), 0)
|
|
19
|
+
|
|
20
|
+
const falseRequest = new TestPikkuRequest(false)
|
|
21
|
+
assert.equal(await falseRequest.data(), false)
|
|
22
|
+
|
|
23
|
+
const emptyStringRequest = new TestPikkuRequest('')
|
|
24
|
+
assert.equal(await emptyStringRequest.data(), '')
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('throws when data is undefined', async () => {
|
|
29
|
+
const request = new TestPikkuRequest(undefined as never)
|
|
30
|
+
|
|
31
|
+
await assert.rejects(() => request.data(), {
|
|
32
|
+
message: 'Data not found',
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
})
|
package/src/pikku-request.ts
CHANGED
|
@@ -15,7 +15,7 @@ export abstract class PikkuRequest<In = any> {
|
|
|
15
15
|
* @returns A promise that resolves to an object containing the combined data.
|
|
16
16
|
*/
|
|
17
17
|
public async data(): Promise<In> {
|
|
18
|
-
if (
|
|
18
|
+
if (this.#data === undefined) {
|
|
19
19
|
throw new Error('Data not found')
|
|
20
20
|
}
|
|
21
21
|
return this.#data
|
package/src/pikku-state.ts
CHANGED
|
@@ -135,6 +135,7 @@ const createEmptyPackageState = (): PikkuPackageState => ({
|
|
|
135
135
|
middleware: {
|
|
136
136
|
tagGroup: {},
|
|
137
137
|
httpGroup: {},
|
|
138
|
+
global: [] as any,
|
|
138
139
|
},
|
|
139
140
|
channelMiddleware: {
|
|
140
141
|
tagGroup: {},
|
|
@@ -142,6 +143,7 @@ const createEmptyPackageState = (): PikkuPackageState => ({
|
|
|
142
143
|
permissions: {
|
|
143
144
|
tagGroup: {},
|
|
144
145
|
httpGroup: {},
|
|
146
|
+
global: [] as any,
|
|
145
147
|
},
|
|
146
148
|
misc: {
|
|
147
149
|
errors: new Map(),
|
|
@@ -150,9 +152,6 @@ const createEmptyPackageState = (): PikkuPackageState => ({
|
|
|
150
152
|
channelMiddleware: {},
|
|
151
153
|
permissions: {},
|
|
152
154
|
},
|
|
153
|
-
models: {
|
|
154
|
-
config: null,
|
|
155
|
-
},
|
|
156
155
|
package: {
|
|
157
156
|
factories: null,
|
|
158
157
|
singletonServices: null,
|
|
@@ -29,6 +29,24 @@ describe('run-tests.sh discovery behavior', () => {
|
|
|
29
29
|
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
30
30
|
})
|
|
31
31
|
|
|
32
|
+
test('returns success in CI when no tests are found', () => {
|
|
33
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pikku-core-tests-'))
|
|
34
|
+
const srcDir = path.join(tempDir, 'src')
|
|
35
|
+
fs.mkdirSync(srcDir, { recursive: true })
|
|
36
|
+
fs.copyFileSync(runTestsScriptPath, path.join(tempDir, 'run-tests.sh'))
|
|
37
|
+
|
|
38
|
+
const result = spawnSync('bash', ['run-tests.sh'], {
|
|
39
|
+
cwd: tempDir,
|
|
40
|
+
encoding: 'utf8',
|
|
41
|
+
env: { ...process.env, CI: 'true' },
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
assert.equal(result.status, 0)
|
|
45
|
+
assert.match(result.stdout, /No test files found matching pattern/)
|
|
46
|
+
|
|
47
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
48
|
+
})
|
|
49
|
+
|
|
32
50
|
test('returns failure in strict mode when no tests are found', () => {
|
|
33
51
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pikku-core-tests-'))
|
|
34
52
|
const srcDir = path.join(tempDir, 'src')
|
|
@@ -30,6 +30,7 @@ export type AIAgentStepResult = {
|
|
|
30
30
|
toolResults: { toolCallId: string; toolName: string; result: unknown }[]
|
|
31
31
|
usage: { inputTokens: number; outputTokens: number }
|
|
32
32
|
finishReason: 'stop' | 'tool-calls' | 'length' | 'error' | 'unknown'
|
|
33
|
+
reasoningContent?: string
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
export interface AIAgentRunnerService {
|