@pikku/core 0.12.62 → 0.12.64
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 +153 -0
- package/dist/dev/hot-reload.js +13 -55
- package/dist/dev/module-runner.d.ts +14 -0
- package/dist/dev/module-runner.js +59 -0
- package/dist/dev/reload-meta.d.ts +13 -0
- package/dist/dev/reload-meta.js +22 -0
- package/dist/errors/errors.d.ts +12 -0
- package/dist/errors/errors.js +19 -0
- package/dist/function/function-runner.d.ts +5 -5
- package/dist/function/function-runner.js +39 -111
- package/dist/function/functions.types.d.ts +17 -7
- package/dist/index.d.ts +5 -1
- package/dist/index.js +3 -1
- package/dist/middleware/auth-apikey.d.ts +1 -0
- package/dist/middleware/auth-bearer.d.ts +1 -0
- package/dist/middleware/auth-cookie.d.ts +1 -0
- package/dist/middleware/cors.d.ts +1 -0
- package/dist/middleware/index.d.ts +1 -1
- package/dist/middleware/index.js +1 -1
- package/dist/middleware/remote-auth.d.ts +1 -0
- package/dist/middleware/telemetry.d.ts +2 -0
- package/dist/middleware/timeout.d.ts +1 -0
- package/dist/permissions.d.ts +32 -25
- package/dist/permissions.js +104 -185
- package/dist/pikku-state.js +0 -2
- package/dist/scopes.d.ts +17 -0
- package/dist/scopes.js +59 -0
- package/dist/services/ai-embedding-service.d.ts +31 -0
- package/dist/services/ai-embedding-service.js +1 -0
- package/dist/services/credential-wire-service.d.ts +3 -1
- package/dist/services/credential-wire-service.js +10 -4
- package/dist/services/index.d.ts +4 -0
- package/dist/services/index.js +2 -0
- package/dist/services/meta-service.d.ts +15 -2
- package/dist/services/meta-service.js +51 -1
- package/dist/services/queue-webhook-service.d.ts +55 -0
- package/dist/services/queue-webhook-service.js +136 -0
- package/dist/services/scope-service.d.ts +67 -0
- package/dist/services/scope-service.js +1 -0
- package/dist/services/typed-secret-service.d.ts +8 -0
- package/dist/services/typed-secret-service.js +39 -4
- package/dist/services/webhook-service.d.ts +140 -0
- package/dist/services/webhook-service.js +44 -0
- package/dist/services/workflow-service.d.ts +7 -0
- package/dist/types/core.types.d.ts +34 -6
- package/dist/types/state.types.d.ts +20 -2
- package/dist/utils/hmac.d.ts +16 -0
- package/dist/utils/hmac.js +26 -0
- package/dist/utils/safe-fetch.d.ts +51 -0
- package/dist/utils/safe-fetch.js +192 -0
- package/dist/wirings/ai-agent/ai-agent-agui.d.ts +84 -0
- package/dist/wirings/ai-agent/ai-agent-agui.js +305 -0
- package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +41 -1
- package/dist/wirings/ai-agent/ai-agent-prepare.js +146 -6
- package/dist/wirings/ai-agent/ai-agent-runner.js +6 -1
- package/dist/wirings/ai-agent/ai-agent-stream.js +61 -10
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +27 -1
- package/dist/wirings/ai-agent/index.d.ts +1 -0
- package/dist/wirings/ai-agent/index.js +1 -0
- package/dist/wirings/ai-agent/voice-input.d.ts +1 -0
- package/dist/wirings/ai-agent/voice-input.js +2 -38
- package/dist/wirings/ai-agent/voice-output.d.ts +1 -0
- package/dist/wirings/channel/channel-common.js +0 -1
- package/dist/wirings/channel/channel-handler.js +1 -4
- package/dist/wirings/channel/channel-middleware-runner.d.ts +12 -0
- package/dist/wirings/channel/channel-middleware-runner.js +34 -19
- package/dist/wirings/channel/channel.types.d.ts +2 -6
- package/dist/wirings/cli/cli-runner.js +0 -2
- package/dist/wirings/cli/cli.types.d.ts +1 -2
- package/dist/wirings/http/http-routes.js +0 -3
- package/dist/wirings/http/http-runner.d.ts +9 -29
- package/dist/wirings/http/http-runner.js +13 -35
- package/dist/wirings/http/http.types.d.ts +1 -9
- package/dist/wirings/mcp/mcp-runner.js +0 -2
- package/dist/wirings/mcp/mcp.types.d.ts +5 -11
- package/dist/wirings/oauth2/index.d.ts +0 -3
- package/dist/wirings/oauth2/index.js +1 -2
- package/dist/wirings/rpc/addon-runner.d.ts +28 -0
- package/dist/wirings/rpc/addon-runner.js +173 -0
- package/dist/wirings/rpc/index.d.ts +5 -1
- package/dist/wirings/rpc/index.js +3 -1
- package/dist/wirings/rpc/remote-addon-auth.d.ts +26 -0
- package/dist/wirings/rpc/remote-addon-auth.js +43 -0
- package/dist/wirings/rpc/rpc-runner.d.ts +19 -0
- package/dist/wirings/rpc/rpc-runner.js +109 -3
- package/dist/wirings/rpc/rpc-types.d.ts +15 -1
- package/dist/wirings/rpc/wire-addon.js +9 -0
- package/dist/wirings/rpc/wire-remote-addon.d.ts +47 -0
- package/dist/wirings/rpc/wire-remote-addon.js +19 -0
- package/dist/wirings/scope/index.d.ts +3 -0
- package/dist/wirings/scope/index.js +2 -0
- package/dist/wirings/scope/scope.types.d.ts +39 -0
- package/dist/wirings/scope/scope.types.js +1 -0
- package/dist/wirings/scope/validate-scope-definitions.d.ts +16 -0
- package/dist/wirings/scope/validate-scope-definitions.js +76 -0
- package/dist/wirings/scope/wire-scope.d.ts +33 -0
- package/dist/wirings/scope/wire-scope.js +32 -0
- package/dist/wirings/workflow/dsl/index.d.ts +1 -1
- package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +53 -1
- package/dist/wirings/workflow/graph/graph-node.d.ts +2 -0
- package/dist/wirings/workflow/graph/graph-runner.js +3 -0
- package/dist/wirings/workflow/graph/wire-workflow-graph.d.ts +6 -0
- package/dist/wirings/workflow/graph/wire-workflow-graph.js +1 -0
- package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +5 -0
- package/dist/wirings/workflow/index.d.ts +2 -2
- package/dist/wirings/workflow/index.js +1 -1
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +51 -1
- package/dist/wirings/workflow/pikku-workflow-service.js +171 -0
- package/dist/wirings/workflow/workflow.types.d.ts +1 -1
- package/package.json +5 -1
- package/run-tests.sh +1 -0
- package/src/dev/hot-reload.ts +13 -68
- package/src/dev/module-runner.test.ts +169 -0
- package/src/dev/module-runner.ts +103 -0
- package/src/dev/reload-meta.test.ts +31 -2
- package/src/dev/reload-meta.ts +26 -0
- package/src/errors/errors.ts +24 -0
- package/src/function/function-runner.test.ts +276 -142
- package/src/function/function-runner.ts +52 -145
- package/src/function/functions.types.ts +28 -38
- package/src/index.ts +8 -6
- package/src/middleware/index.ts +1 -5
- package/src/permissions.test.ts +153 -372
- package/src/permissions.ts +139 -259
- package/src/pikku-state.ts +0 -2
- package/src/scopes.test.ts +167 -0
- package/src/scopes.ts +69 -0
- package/src/services/ai-embedding-service.ts +31 -0
- package/src/services/credential-wire-service.ts +10 -4
- package/src/services/index.ts +20 -0
- package/src/services/meta-service.ts +68 -3
- package/src/services/queue-webhook-service.test.ts +408 -0
- package/src/services/queue-webhook-service.ts +182 -0
- package/src/services/scope-service.ts +75 -0
- package/src/services/typed-secret-service.test.ts +35 -0
- package/src/services/typed-secret-service.ts +39 -4
- package/src/services/webhook-service.ts +180 -0
- package/src/services/workflow-service.ts +7 -0
- package/src/types/core.types.ts +42 -14
- package/src/types/state.types.ts +23 -3
- package/src/utils/hmac.ts +27 -0
- package/src/utils/safe-fetch.test.ts +373 -0
- package/src/utils/safe-fetch.ts +213 -0
- package/src/wirings/ai-agent/ai-agent-agui.test.ts +1127 -0
- package/src/wirings/ai-agent/ai-agent-agui.ts +386 -0
- package/src/wirings/ai-agent/ai-agent-prepare.test.ts +308 -0
- package/src/wirings/ai-agent/ai-agent-prepare.ts +192 -8
- package/src/wirings/ai-agent/ai-agent-runner.test.ts +48 -1
- package/src/wirings/ai-agent/ai-agent-runner.ts +21 -0
- package/src/wirings/ai-agent/ai-agent-stream.test.ts +198 -10
- package/src/wirings/ai-agent/ai-agent-stream.ts +84 -10
- package/src/wirings/ai-agent/ai-agent.types.ts +28 -0
- package/src/wirings/ai-agent/index.ts +1 -0
- package/src/wirings/ai-agent/voice-input.ts +6 -35
- package/src/wirings/channel/channel-common.ts +0 -1
- package/src/wirings/channel/channel-handler.ts +1 -9
- package/src/wirings/channel/channel-middleware-runner.test.ts +28 -2
- package/src/wirings/channel/channel-middleware-runner.ts +39 -27
- package/src/wirings/channel/channel.types.ts +0 -6
- package/src/wirings/cli/cli-runner.ts +0 -2
- package/src/wirings/cli/cli.types.ts +0 -2
- package/src/wirings/http/http-routes.ts +0 -3
- package/src/wirings/http/http-runner.test.ts +1 -11
- package/src/wirings/http/http-runner.ts +19 -40
- package/src/wirings/http/http.types.ts +1 -10
- package/src/wirings/mcp/mcp-runner.ts +0 -2
- package/src/wirings/mcp/mcp.types.ts +3 -11
- package/src/wirings/oauth2/index.ts +0 -3
- package/src/wirings/rpc/addon-runner.ts +233 -0
- package/src/wirings/rpc/index.ts +17 -1
- package/src/wirings/rpc/remote-addon-auth.ts +69 -0
- package/src/wirings/rpc/rpc-runner.test.ts +319 -16
- package/src/wirings/rpc/rpc-runner.ts +155 -3
- package/src/wirings/rpc/rpc-types.ts +16 -5
- package/src/wirings/rpc/wire-addon.test.ts +6 -3
- package/src/wirings/rpc/wire-addon.ts +9 -0
- package/src/wirings/rpc/wire-remote-addon.ts +57 -0
- package/src/wirings/scope/index.ts +14 -0
- package/src/wirings/scope/scope.test.ts +135 -0
- package/src/wirings/scope/scope.types.ts +44 -0
- package/src/wirings/scope/validate-scope-definitions.ts +110 -0
- package/src/wirings/scope/wire-scope.ts +34 -0
- package/src/wirings/workflow/dsl/index.ts +4 -0
- package/src/wirings/workflow/dsl/workflow-dsl.types.ts +61 -0
- package/src/wirings/workflow/graph/graph-node.ts +2 -0
- package/src/wirings/workflow/graph/graph-runner.test.ts +58 -0
- package/src/wirings/workflow/graph/graph-runner.ts +3 -0
- package/src/wirings/workflow/graph/wire-workflow-graph.ts +7 -0
- package/src/wirings/workflow/graph/workflow-graph.types.ts +5 -0
- package/src/wirings/workflow/index.ts +5 -0
- package/src/wirings/workflow/pikku-workflow-service.test.ts +341 -0
- package/src/wirings/workflow/pikku-workflow-service.ts +236 -0
- package/src/wirings/workflow/workflow-step-session.test.ts +21 -5
- package/src/wirings/workflow/workflow.types.ts +4 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/src/wirings/oauth2/oauth2-client.test.ts +0 -930
- package/src/wirings/oauth2/oauth2-client.ts +0 -331
- package/src/wirings/oauth2/oauth2-routes.ts +0 -234
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { describe, test } from 'node:test'
|
|
2
|
+
import * as assert from 'node:assert'
|
|
3
|
+
import { verifyScopes } from './scopes.js'
|
|
4
|
+
import { MissingScopeError } from './errors/errors.js'
|
|
5
|
+
import type { CoreUserSession } from './types/core.types.js'
|
|
6
|
+
|
|
7
|
+
const session = (scopes?: string[]): CoreUserSession =>
|
|
8
|
+
({ userId: 'test-user', scopes }) as CoreUserSession
|
|
9
|
+
|
|
10
|
+
describe('verifyScopes', () => {
|
|
11
|
+
test('passes on an exact match', () => {
|
|
12
|
+
verifyScopes(['invoices:create'], session(['invoices:create']))
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test('passes when no scopes are required', () => {
|
|
16
|
+
verifyScopes([], session())
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('throws when the session holds none', () => {
|
|
20
|
+
assert.throws(
|
|
21
|
+
() => verifyScopes(['invoices:create'], session([])),
|
|
22
|
+
MissingScopeError
|
|
23
|
+
)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('holding an unrelated scope does not grant', () => {
|
|
27
|
+
assert.throws(
|
|
28
|
+
() => verifyScopes(['invoices:create'], session(['billing:read'])),
|
|
29
|
+
MissingScopeError
|
|
30
|
+
)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
describe('wildcards', () => {
|
|
34
|
+
test('admin:* satisfies a descendant', () => {
|
|
35
|
+
verifyScopes(['admin:invoices:create'], session(['admin:*']))
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('admin:* satisfies an intermediate descendant', () => {
|
|
39
|
+
verifyScopes(['admin:invoices'], session(['admin:*']))
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('admin:* satisfies the parent itself', () => {
|
|
43
|
+
verifyScopes(['admin'], session(['admin:*']))
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('a bare * satisfies anything', () => {
|
|
47
|
+
verifyScopes(['admin:invoices:create'], session(['*']))
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('a deep wildcard satisfies its own descendants', () => {
|
|
51
|
+
verifyScopes(['admin:invoices:create'], session(['admin:invoices:*']))
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('does not leak across siblings', () => {
|
|
55
|
+
assert.throws(
|
|
56
|
+
() => verifyScopes(['billing:read'], session(['admin:*'])),
|
|
57
|
+
MissingScopeError
|
|
58
|
+
)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('a required wildcard is not satisfied by one narrow grant', () => {
|
|
62
|
+
assert.throws(
|
|
63
|
+
() => verifyScopes(['admin:*'], session(['admin:invoices:create'])),
|
|
64
|
+
MissingScopeError
|
|
65
|
+
)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('a required wildcard is satisfied by a broader grant', () => {
|
|
69
|
+
verifyScopes(['admin:*'], session(['*']))
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
describe('a plain parent grant covers its descendants', () => {
|
|
74
|
+
test('holding a parent satisfies a deep descendant', () => {
|
|
75
|
+
verifyScopes(['admin:invoices:create'], session(['admin']))
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('holding a parent satisfies an intermediate descendant', () => {
|
|
79
|
+
verifyScopes(['admin:invoices'], session(['admin']))
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('holding an intermediate parent satisfies its child', () => {
|
|
83
|
+
verifyScopes(['admin:invoices:create'], session(['admin:invoices']))
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test('a parent does not leak across siblings', () => {
|
|
87
|
+
assert.throws(
|
|
88
|
+
() => verifyScopes(['billing:read'], session(['admin'])),
|
|
89
|
+
MissingScopeError
|
|
90
|
+
)
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
describe('AND semantics', () => {
|
|
95
|
+
test('passes only when every required scope is held', () => {
|
|
96
|
+
verifyScopes(
|
|
97
|
+
['invoices:read', 'invoices:write'],
|
|
98
|
+
session(['invoices:read', 'invoices:write'])
|
|
99
|
+
)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test('throws when only one of two is held', () => {
|
|
103
|
+
assert.throws(
|
|
104
|
+
() =>
|
|
105
|
+
verifyScopes(
|
|
106
|
+
['invoices:read', 'invoices:write'],
|
|
107
|
+
session(['invoices:read'])
|
|
108
|
+
),
|
|
109
|
+
MissingScopeError
|
|
110
|
+
)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test('one wildcard can satisfy several requirements', () => {
|
|
114
|
+
verifyScopes(['admin:read', 'admin:write'], session(['admin:*']))
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
describe('fails closed', () => {
|
|
119
|
+
test('throws when the session has no scopes field', () => {
|
|
120
|
+
assert.throws(
|
|
121
|
+
() => verifyScopes(['invoices:create'], session(undefined)),
|
|
122
|
+
MissingScopeError
|
|
123
|
+
)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test('throws when there is no session at all', () => {
|
|
127
|
+
assert.throws(
|
|
128
|
+
() => verifyScopes(['invoices:create'], undefined),
|
|
129
|
+
MissingScopeError
|
|
130
|
+
)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test('a narrower grant does not satisfy a broader requirement', () => {
|
|
134
|
+
assert.throws(
|
|
135
|
+
() => verifyScopes(['admin'], session(['admin:invoices'])),
|
|
136
|
+
MissingScopeError
|
|
137
|
+
)
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
describe('a parent grant coexists with a child grant', () => {
|
|
142
|
+
test('the parent still grants when a child is also held', () => {
|
|
143
|
+
verifyScopes(
|
|
144
|
+
['admin:invoices'],
|
|
145
|
+
session(['admin:invoices', 'admin:invoices:create'])
|
|
146
|
+
)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test('the child still grants when a parent is also held', () => {
|
|
150
|
+
verifyScopes(
|
|
151
|
+
['admin:invoices:create'],
|
|
152
|
+
session(['admin:invoices', 'admin:invoices:create'])
|
|
153
|
+
)
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
test('names the missing scope', () => {
|
|
158
|
+
assert.throws(
|
|
159
|
+
() => verifyScopes(['a:b', 'c:d'], session(['a:b'])),
|
|
160
|
+
(err: MissingScopeError) => {
|
|
161
|
+
assert.equal(err.payload.scope, 'c:d')
|
|
162
|
+
assert.match(err.message, /c:d/)
|
|
163
|
+
return true
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
})
|
|
167
|
+
})
|
package/src/scopes.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { CoreUserSession } from './types/core.types.js'
|
|
2
|
+
import { MissingScopeError } from './errors/errors.js'
|
|
3
|
+
|
|
4
|
+
const SEPARATOR = ':'
|
|
5
|
+
const WILDCARD = '*'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Builds every grant that would satisfy `scope`: the scope itself, a wildcard
|
|
9
|
+
* directly beneath it, a wildcard at each ancestor level, and each plain
|
|
10
|
+
* ancestor id — holding a parent scope grants everything nested beneath it.
|
|
11
|
+
*
|
|
12
|
+
* For `admin:invoices:create` that is:
|
|
13
|
+
* admin:invoices:create, admin:invoices:create:*, *, admin:*, admin:invoices:*,
|
|
14
|
+
* admin, admin:invoices
|
|
15
|
+
*/
|
|
16
|
+
const satisfyingGrants = (scope: string): string[] => {
|
|
17
|
+
const segments = scope.split(SEPARATOR)
|
|
18
|
+
const grants = [scope, `${scope}${SEPARATOR}${WILDCARD}`]
|
|
19
|
+
for (let i = 0; i < segments.length; i++) {
|
|
20
|
+
grants.push([...segments.slice(0, i), WILDCARD].join(SEPARATOR))
|
|
21
|
+
}
|
|
22
|
+
for (let i = 1; i < segments.length; i++) {
|
|
23
|
+
grants.push(segments.slice(0, i).join(SEPARATOR))
|
|
24
|
+
}
|
|
25
|
+
return grants
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Checks whether `held` satisfies a single required scope.
|
|
30
|
+
*
|
|
31
|
+
* A grant satisfies a requirement when it is the scope itself, a plain ancestor
|
|
32
|
+
* (`admin` covers `admin:invoices:create`), a wildcard at or above it (`admin:*`
|
|
33
|
+
* covers `admin` and `admin:invoices:create`), or the bare `*`. A narrower grant
|
|
34
|
+
* never satisfies a broader requirement — `admin:invoices` does not grant
|
|
35
|
+
* `admin`.
|
|
36
|
+
*/
|
|
37
|
+
const holds = (held: ReadonlySet<string>, scope: string): boolean =>
|
|
38
|
+
satisfyingGrants(scope).some((grant) => held.has(grant))
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Verifies that a session holds every required scope, throwing on the first
|
|
42
|
+
* one it does not.
|
|
43
|
+
*
|
|
44
|
+
* Scopes are an AND gate: every entry in `required` must be satisfied. This is
|
|
45
|
+
* deliberately distinct from `permissions`, which OR together — a scope can
|
|
46
|
+
* only ever narrow access, so adding one to a function can never widen it.
|
|
47
|
+
*
|
|
48
|
+
* Fails closed: a session without a `scopes` field, or no session at all,
|
|
49
|
+
* satisfies nothing.
|
|
50
|
+
*
|
|
51
|
+
* @param required - Scopes the function declares. Empty means no gate.
|
|
52
|
+
* @param session - The session to check. May be undefined.
|
|
53
|
+
* @throws {MissingScopeError} Naming the first unsatisfied scope.
|
|
54
|
+
*/
|
|
55
|
+
export const verifyScopes = (
|
|
56
|
+
required: readonly string[] | undefined,
|
|
57
|
+
session: CoreUserSession | undefined
|
|
58
|
+
): void => {
|
|
59
|
+
if (!required || required.length === 0) {
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const held = new Set(session?.scopes ?? [])
|
|
64
|
+
for (const scope of required) {
|
|
65
|
+
if (!holds(held, scope)) {
|
|
66
|
+
throw new MissingScopeError(scope)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A narrow, dedicated embedding service.
|
|
3
|
+
*
|
|
4
|
+
* Vector stores (Qdrant, Pinecone, Supabase/pgvector, …) depend on this to turn
|
|
5
|
+
* text into vectors, at BOTH index time (ingest) and query time (search). The
|
|
6
|
+
* embedding model is fixed at construction so those two moments cannot drift
|
|
7
|
+
* into different vector spaces — a mismatch would silently break similarity
|
|
8
|
+
* search. Provider addons (e.g. `@pikku/addon-openai`) implement it; the app
|
|
9
|
+
* wires a single instance into `singletonServices.aiEmbedding`.
|
|
10
|
+
*
|
|
11
|
+
* This is deliberately smaller than {@link AIAgentRunnerService} (whose optional
|
|
12
|
+
* `embed`/`embedMany` take a per-call model): a vector store should not have to
|
|
13
|
+
* pull in the whole agent-runner tool loop just to embed, and pinning the model
|
|
14
|
+
* to the service is what guarantees index/query consistency.
|
|
15
|
+
*
|
|
16
|
+
* Documents (ingested corpus) and queries (search text) are embedded through
|
|
17
|
+
* separate methods because some models are asymmetric — Cohere's `input_type`,
|
|
18
|
+
* E5's `query:`/`passage:` prefixes, BGE's query instruction — and must know
|
|
19
|
+
* which side they are embedding to produce comparable vectors. Symmetric
|
|
20
|
+
* providers (e.g. OpenAI) point both methods at the same call.
|
|
21
|
+
*/
|
|
22
|
+
export interface AIEmbeddingService {
|
|
23
|
+
/** The embedding model backing this service. Index and query share it. */
|
|
24
|
+
readonly model: string
|
|
25
|
+
/** The vector dimensionality this model produces, when known. */
|
|
26
|
+
readonly dimensions?: number
|
|
27
|
+
/** Embed corpus documents (index time), preserving input order. */
|
|
28
|
+
embedDocuments(values: string[]): Promise<number[][]>
|
|
29
|
+
/** Embed a search query (query time) into a vector. */
|
|
30
|
+
embedQuery(value: string): Promise<number[]>
|
|
31
|
+
}
|
|
@@ -9,16 +9,22 @@ export class PikkuCredentialWireService {
|
|
|
9
9
|
|
|
10
10
|
constructor(
|
|
11
11
|
private credentialService?: CredentialService,
|
|
12
|
-
private wire?: PikkuRawWire
|
|
12
|
+
private wire?: PikkuRawWire,
|
|
13
|
+
private aliases?: Record<string, string>
|
|
13
14
|
) {}
|
|
14
15
|
|
|
16
|
+
private resolveName(name: string): string {
|
|
17
|
+
return this.aliases?.[name] ?? name
|
|
18
|
+
}
|
|
19
|
+
|
|
15
20
|
set(name: string, value: unknown): void {
|
|
16
|
-
this.credentials[name] = value
|
|
21
|
+
this.credentials[this.resolveName(name)] = value
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
get<T = unknown>(name: string): T | null | Promise<T | null> {
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
const key = this.resolveName(name)
|
|
26
|
+
if (this.loaded) return (this.credentials[key] as T) ?? null
|
|
27
|
+
return this.lazyLoad().then(() => (this.credentials[key] as T) ?? null)
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
getAll(): Record<string, unknown> | Promise<Record<string, unknown>> {
|
package/src/services/index.ts
CHANGED
|
@@ -22,6 +22,10 @@ export { LocalCredentialService } from './local-credential-service.js'
|
|
|
22
22
|
export { LocalVariablesService } from './local-variables.js'
|
|
23
23
|
export { ConsoleLogger, JsonConsoleLogger } from './logger-console.js'
|
|
24
24
|
export { InMemoryWorkflowService } from './in-memory-workflow-service.js'
|
|
25
|
+
export {
|
|
26
|
+
QueueWebhookService,
|
|
27
|
+
pikkuWebhookWorkerFunc,
|
|
28
|
+
} from './queue-webhook-service.js'
|
|
25
29
|
export { InMemoryQueueService } from './in-memory-queue-service.js'
|
|
26
30
|
export { InMemoryTriggerService } from './in-memory-trigger-service.js'
|
|
27
31
|
export { InMemoryAIRunStateService } from './in-memory-ai-run-state-service.js'
|
|
@@ -56,6 +60,20 @@ export type {
|
|
|
56
60
|
SendTemplateEmailInput,
|
|
57
61
|
SendTextEmailInput,
|
|
58
62
|
} from './email-service.js'
|
|
63
|
+
export {
|
|
64
|
+
DEFAULT_WEBHOOK_RETRIES,
|
|
65
|
+
DEFAULT_WEBHOOK_SIGNATURE_HEADER,
|
|
66
|
+
PIKKU_OUTGOING_WEBHOOK_QUEUE_NAME,
|
|
67
|
+
WebhookService,
|
|
68
|
+
type SendWebhookInput,
|
|
69
|
+
type SendWebhookResult,
|
|
70
|
+
type WebhookAttemptRecord,
|
|
71
|
+
type WebhookAttemptResult,
|
|
72
|
+
type WebhookDeliveryRecord,
|
|
73
|
+
type WebhookDeliveryWithAttempts,
|
|
74
|
+
type WebhookJobData,
|
|
75
|
+
type WebhookServiceConfig,
|
|
76
|
+
} from './webhook-service.js'
|
|
59
77
|
export type { Logger } from './logger.js'
|
|
60
78
|
export type { SecretService } from './secret-service.js'
|
|
61
79
|
export type { VariablesService } from './variables-service.js'
|
|
@@ -95,6 +113,7 @@ export type {
|
|
|
95
113
|
AITranscriptionResult,
|
|
96
114
|
AIAgentRunnerService,
|
|
97
115
|
} from './ai-agent-runner-service.js'
|
|
116
|
+
export type { AIEmbeddingService } from './ai-embedding-service.js'
|
|
98
117
|
export type {
|
|
99
118
|
CreateRunInput,
|
|
100
119
|
AIRunStateService,
|
|
@@ -112,6 +131,7 @@ export type {
|
|
|
112
131
|
export type { VariableStatus, VariableMeta } from './typed-variables-service.js'
|
|
113
132
|
export type { MetaService } from './meta-service.js'
|
|
114
133
|
export type { SessionStore } from './session-store.js'
|
|
134
|
+
export type { ScopeService, Role } from './scope-service.js'
|
|
115
135
|
export {
|
|
116
136
|
NoopAuditService,
|
|
117
137
|
createInvocationAudit,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { readFile, readdir } from 'node:fs/promises'
|
|
2
|
+
import { existsSync } from 'node:fs'
|
|
3
|
+
import { createRequire } from 'node:module'
|
|
2
4
|
import { join } from 'node:path'
|
|
3
5
|
import type { JSONSchema7 } from 'json-schema'
|
|
4
6
|
import type { HTTPWiringsMeta } from '../wirings/http/http.types.js'
|
|
@@ -107,8 +109,6 @@ export interface PermissionDefinitionMeta {
|
|
|
107
109
|
|
|
108
110
|
export interface PermissionsGroupsMeta {
|
|
109
111
|
definitions: Record<string, PermissionDefinitionMeta>
|
|
110
|
-
httpGroups: Record<string, GroupMeta>
|
|
111
|
-
tagGroups: Record<string, GroupMeta>
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
export interface EmailTemplateLocaleMeta {
|
|
@@ -159,6 +159,20 @@ export interface MetaService {
|
|
|
159
159
|
readDir(relativePath: string): Promise<string[]>
|
|
160
160
|
/** Read a project source file relative to the project root (one level above .pikku). */
|
|
161
161
|
readProjectFile(relativePath: string): Promise<string | null>
|
|
162
|
+
/**
|
|
163
|
+
* Read a file from an *installed addon package's* own `.pikku` directory
|
|
164
|
+
* (resolved from node_modules), NOT the app's `.pikku`. `relativePath` is
|
|
165
|
+
* relative to that package's `.pikku` root (use `../` to reach the package
|
|
166
|
+
* root itself, e.g. `../README.md`). Returns null if the package or file is
|
|
167
|
+
* absent. Only local/Node implementations resolve packages; remote impls omit
|
|
168
|
+
* this and callers guard with `?.`.
|
|
169
|
+
*/
|
|
170
|
+
readPackageFile?(
|
|
171
|
+
packageName: string,
|
|
172
|
+
relativePath: string
|
|
173
|
+
): Promise<string | null>
|
|
174
|
+
/** List a directory inside an installed addon package's `.pikku`. */
|
|
175
|
+
readPackageDir?(packageName: string, relativePath: string): Promise<string[]>
|
|
162
176
|
|
|
163
177
|
// -- Typed metadata accessors --
|
|
164
178
|
|
|
@@ -252,6 +266,57 @@ export class LocalMetaService implements MetaService {
|
|
|
252
266
|
}
|
|
253
267
|
}
|
|
254
268
|
|
|
269
|
+
// Resolve an installed addon package's root dir from the app's node_modules.
|
|
270
|
+
// The addon packages have no main `exports` entry, so `require.resolve(pkg)`
|
|
271
|
+
// throws; instead we walk the node_modules search paths (anchored at the app
|
|
272
|
+
// root, one level above `.pikku`) and pick the first that contains the package.
|
|
273
|
+
private packageRootCache = new Map<string, string | null>()
|
|
274
|
+
private resolvePackageRoot(packageName: string): string | null {
|
|
275
|
+
const cached = this.packageRootCache.get(packageName)
|
|
276
|
+
if (cached !== undefined) return cached
|
|
277
|
+
let root: string | null = null
|
|
278
|
+
try {
|
|
279
|
+
const require = createRequire(join(this.basePath, '..', 'index.js'))
|
|
280
|
+
for (const dir of require.resolve.paths(packageName) ?? []) {
|
|
281
|
+
const candidate = join(dir, packageName)
|
|
282
|
+
if (existsSync(join(candidate, 'package.json'))) {
|
|
283
|
+
root = candidate
|
|
284
|
+
break
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
} catch {
|
|
288
|
+
root = null
|
|
289
|
+
}
|
|
290
|
+
this.packageRootCache.set(packageName, root)
|
|
291
|
+
return root
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async readPackageFile(
|
|
295
|
+
packageName: string,
|
|
296
|
+
relativePath: string
|
|
297
|
+
): Promise<string | null> {
|
|
298
|
+
const root = this.resolvePackageRoot(packageName)
|
|
299
|
+
if (!root) return null
|
|
300
|
+
try {
|
|
301
|
+
return await readFile(join(root, '.pikku', relativePath), 'utf-8')
|
|
302
|
+
} catch {
|
|
303
|
+
return null
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async readPackageDir(
|
|
308
|
+
packageName: string,
|
|
309
|
+
relativePath: string
|
|
310
|
+
): Promise<string[]> {
|
|
311
|
+
const root = this.resolvePackageRoot(packageName)
|
|
312
|
+
if (!root) return []
|
|
313
|
+
try {
|
|
314
|
+
return await readdir(join(root, '.pikku', relativePath))
|
|
315
|
+
} catch {
|
|
316
|
+
return []
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
255
320
|
clearCache(): void {
|
|
256
321
|
this.httpMetaCache = null
|
|
257
322
|
this.channelsMetaCache = null
|
|
@@ -489,7 +554,7 @@ export class LocalMetaService implements MetaService {
|
|
|
489
554
|
)
|
|
490
555
|
this.permissionsGroupsMetaCache = content
|
|
491
556
|
? JSON.parse(content)
|
|
492
|
-
: { definitions: {}
|
|
557
|
+
: { definitions: {} }
|
|
493
558
|
return this.permissionsGroupsMetaCache!
|
|
494
559
|
}
|
|
495
560
|
|