@pikku/core 0.12.63 → 0.12.66
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 +368 -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 +42 -27
- package/dist/permissions.js +101 -203
- 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/testing/service-tests.js +37 -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 +105 -1
- package/dist/wirings/ai-agent/ai-agent-prepare.js +249 -11
- package/dist/wirings/ai-agent/ai-agent-runner.js +10 -1
- package/dist/wirings/ai-agent/ai-agent-stream.js +88 -16
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +56 -2
- package/dist/wirings/ai-agent/index.d.ts +2 -1
- package/dist/wirings/ai-agent/index.js +2 -1
- package/dist/wirings/ai-agent/voice-input.d.ts +1 -0
- package/dist/wirings/ai-agent/voice-input.js +5 -41
- 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 +3 -2
- package/dist/wirings/cli/cli.types.d.ts +1 -2
- package/dist/wirings/cli/command-parser.d.ts +2 -0
- package/dist/wirings/cli/command-parser.js +59 -2
- package/dist/wirings/credential/credential.types.d.ts +14 -0
- package/dist/wirings/credential/validate-credential-definitions.js +1 -0
- package/dist/wirings/gateway/gateway-runner.js +100 -50
- package/dist/wirings/gateway/gateway.types.d.ts +8 -5
- 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 +9 -34
- package/dist/wirings/http/http.types.d.ts +4 -12
- 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/secret/secret.types.d.ts +14 -0
- package/dist/wirings/secret/validate-secret-definitions.js +2 -0
- package/dist/wirings/variable/validate-variable-definitions.js +2 -0
- package/dist/wirings/variable/variable.types.d.ts +14 -0
- package/dist/wirings/workflow/dsl/index.d.ts +1 -1
- package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +89 -7
- 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 +59 -1
- package/dist/wirings/workflow/pikku-workflow-service.js +187 -0
- package/dist/wirings/workflow/workflow.types.d.ts +1 -3
- package/package.json +6 -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 +160 -373
- package/src/permissions.ts +137 -279
- 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/testing/service-tests.ts +49 -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-authorization.test.ts +204 -0
- package/src/wirings/ai-agent/ai-agent-prepare.test.ts +483 -0
- package/src/wirings/ai-agent/ai-agent-prepare.ts +324 -13
- package/src/wirings/ai-agent/ai-agent-resume-authorization.test.ts +207 -0
- package/src/wirings/ai-agent/ai-agent-runner.test.ts +48 -1
- package/src/wirings/ai-agent/ai-agent-runner.ts +28 -0
- package/src/wirings/ai-agent/ai-agent-stream.test.ts +301 -10
- package/src/wirings/ai-agent/ai-agent-stream.ts +122 -16
- package/src/wirings/ai-agent/ai-agent.types.ts +57 -0
- package/src/wirings/ai-agent/index.ts +5 -0
- package/src/wirings/ai-agent/voice-input.test.ts +90 -0
- package/src/wirings/ai-agent/voice-input.ts +9 -40
- 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 +4 -2
- package/src/wirings/cli/cli.types.ts +0 -2
- package/src/wirings/cli/command-parser.test.ts +130 -0
- package/src/wirings/cli/command-parser.ts +80 -2
- package/src/wirings/credential/credential.types.ts +14 -0
- package/src/wirings/credential/validate-credential-definitions.ts +1 -0
- package/src/wirings/gateway/gateway-authorization.test.ts +444 -0
- package/src/wirings/gateway/gateway-runner.ts +114 -68
- package/src/wirings/gateway/gateway.types.ts +7 -9
- 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 +16 -39
- package/src/wirings/http/http.types.ts +7 -14
- 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/secret/secret.types.ts +14 -0
- package/src/wirings/secret/validate-secret-definitions.ts +2 -0
- package/src/wirings/variable/validate-variable-definitions.ts +2 -0
- package/src/wirings/variable/variable.types.ts +14 -0
- package/src/wirings/workflow/dsl/index.ts +4 -0
- package/src/wirings/workflow/dsl/workflow-dsl.types.ts +97 -6
- 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 +272 -0
- package/src/wirings/workflow/workflow-on-error.test.ts +154 -0
- package/src/wirings/workflow/workflow-step-session.test.ts +21 -5
- package/src/wirings/workflow/workflow.types.ts +4 -2
- 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,173 @@
|
|
|
1
|
+
import { pikkuState } from '../../pikku-state.js';
|
|
2
|
+
/**
|
|
3
|
+
* Wrap a SecretService so that the logical secret names an addon reads are
|
|
4
|
+
* remapped to the actual project secret names via the instance's overrides.
|
|
5
|
+
*/
|
|
6
|
+
const aliasSecretService = (secrets, overrides) => {
|
|
7
|
+
const map = (key) => overrides[key] ?? key;
|
|
8
|
+
return {
|
|
9
|
+
getSecret: (key) => secrets.getSecret(map(key)),
|
|
10
|
+
hasSecret: (key) => secrets.hasSecret(map(key)),
|
|
11
|
+
setSecret: (key, value) => secrets.setSecret(map(key), value),
|
|
12
|
+
deleteSecret: (key) => secrets.deleteSecret(map(key)),
|
|
13
|
+
getSecrets: async (keys) => {
|
|
14
|
+
const result = await secrets.getSecrets(keys.map(map));
|
|
15
|
+
const out = {};
|
|
16
|
+
for (const logical of keys) {
|
|
17
|
+
const real = map(logical);
|
|
18
|
+
if (real in result)
|
|
19
|
+
out[logical] = result[real];
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Wrap a VariablesService so that the logical variable names an addon reads
|
|
27
|
+
* are remapped to the actual project variable names via the instance's overrides.
|
|
28
|
+
*/
|
|
29
|
+
const aliasVariablesService = (variables, overrides) => {
|
|
30
|
+
const map = (name) => overrides[name] ?? name;
|
|
31
|
+
return {
|
|
32
|
+
get: (name) => variables.get(map(name)),
|
|
33
|
+
getVariables: (names) => {
|
|
34
|
+
const result = variables.getVariables(names.map(map));
|
|
35
|
+
const remap = (r) => {
|
|
36
|
+
const out = {};
|
|
37
|
+
for (const logical of names) {
|
|
38
|
+
const real = map(logical);
|
|
39
|
+
if (real in r)
|
|
40
|
+
out[logical] = r[real];
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
};
|
|
44
|
+
return result instanceof Promise
|
|
45
|
+
? result.then(remap)
|
|
46
|
+
: remap(result);
|
|
47
|
+
},
|
|
48
|
+
getAll: () => variables.getAll(),
|
|
49
|
+
set: (name, value) => variables.set(map(name), value),
|
|
50
|
+
has: (name) => variables.has(map(name)),
|
|
51
|
+
delete: (name) => variables.delete(map(name)),
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Find the consumer-defined namespace (from wireAddon) for a given addon package.
|
|
56
|
+
* Returns null if the package isn't registered as an addon.
|
|
57
|
+
*/
|
|
58
|
+
const findAddonNamespaceForPackage = (packageName) => {
|
|
59
|
+
const addons = pikkuState(null, 'addons', 'packages');
|
|
60
|
+
if (!addons)
|
|
61
|
+
return null;
|
|
62
|
+
for (const [namespace, cfg] of addons.entries()) {
|
|
63
|
+
if (cfg?.package === packageName)
|
|
64
|
+
return namespace;
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Wrap a workflow service so that bare workflow names passed from inside an
|
|
70
|
+
* addon function are auto-prefixed with the addon's consumer-facing namespace.
|
|
71
|
+
* Without this, `runToCompletion('myWorkflow')` from inside an addon misses
|
|
72
|
+
* the workflow registered under the addon's package scope and throws
|
|
73
|
+
* WorkflowNotFoundError — forcing addons to hardcode their consumer-defined
|
|
74
|
+
* namespace, which couples the addon to its caller.
|
|
75
|
+
*
|
|
76
|
+
* Explicit `'ns:name'` and bare names that already exist in root meta are
|
|
77
|
+
* unaffected; only bare names that would otherwise miss resolution get
|
|
78
|
+
* prefixed.
|
|
79
|
+
*/
|
|
80
|
+
const wrapWorkflowServiceForPackage = (service, packageName, namespace) => {
|
|
81
|
+
return new Proxy(service, {
|
|
82
|
+
get(target, prop, receiver) {
|
|
83
|
+
if (prop === 'startWorkflow' || prop === 'runToCompletion') {
|
|
84
|
+
const original = Reflect.get(target, prop, receiver);
|
|
85
|
+
return function (name, ...rest) {
|
|
86
|
+
if (typeof name === 'string' && !name.includes(':')) {
|
|
87
|
+
// Prefer the known instance namespace; fall back to the
|
|
88
|
+
// package's sole namespace when invoked without an instance.
|
|
89
|
+
const ns = namespace ?? findAddonNamespaceForPackage(packageName);
|
|
90
|
+
if (ns) {
|
|
91
|
+
name = `${ns}:${name}`;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return original.call(this, name, ...rest);
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return Reflect.get(target, prop, receiver);
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Get or create singleton services for an addon instance.
|
|
103
|
+
* Services are cached in pikkuState to avoid recreation on each call.
|
|
104
|
+
*
|
|
105
|
+
* @param packageName - The addon package name
|
|
106
|
+
* @param parentServices - The parent/caller's singleton services (used as base)
|
|
107
|
+
* @param addonInstance - The wired instance whose overrides shape the services
|
|
108
|
+
* @returns The instance's singleton services
|
|
109
|
+
*/
|
|
110
|
+
export const getOrCreatePackageSingletonServices = async (packageName, parentServices, addonInstance) => {
|
|
111
|
+
// Cache per instance (namespace), not per package, so two instances of one
|
|
112
|
+
// package get separate services built with their own overrides. A bare
|
|
113
|
+
// package-scoped call (no instance) falls back to per-package caching.
|
|
114
|
+
const cacheKey = addonInstance?.namespace ?? packageName;
|
|
115
|
+
const cachedServices = pikkuState(cacheKey, 'package', 'singletonServices');
|
|
116
|
+
if (cachedServices) {
|
|
117
|
+
return cachedServices;
|
|
118
|
+
}
|
|
119
|
+
const factories = pikkuState(packageName, 'package', 'factories');
|
|
120
|
+
if (!factories || !factories.createSingletonServices) {
|
|
121
|
+
// No factories registered, use parent services
|
|
122
|
+
return parentServices;
|
|
123
|
+
}
|
|
124
|
+
// Apply this instance's secret/variable overrides by aliasing the resolver
|
|
125
|
+
// services the addon reads from, so its createSingletonServices resolves
|
|
126
|
+
// instance-specific secrets/variables.
|
|
127
|
+
let existingServices = parentServices;
|
|
128
|
+
if (addonInstance?.secretOverrides && parentServices.secrets) {
|
|
129
|
+
existingServices = {
|
|
130
|
+
...existingServices,
|
|
131
|
+
secrets: aliasSecretService(parentServices.secrets, addonInstance.secretOverrides),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (addonInstance?.variableOverrides && parentServices.variables) {
|
|
135
|
+
existingServices = {
|
|
136
|
+
...existingServices,
|
|
137
|
+
variables: aliasVariablesService(parentServices.variables, addonInstance.variableOverrides),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
// Create config for the package (use parent config if no factory)
|
|
141
|
+
let config = existingServices.config;
|
|
142
|
+
if (factories.createConfig) {
|
|
143
|
+
config = await factories.createConfig(existingServices.variables);
|
|
144
|
+
}
|
|
145
|
+
// Create singleton services for the package, passing parent services as existing
|
|
146
|
+
const packageServices = await factories.createSingletonServices(config, existingServices);
|
|
147
|
+
// Wrap workflowService so that bare names used inside the addon's functions
|
|
148
|
+
// resolve to workflows registered under the addon's package scope.
|
|
149
|
+
if (packageServices.workflowService &&
|
|
150
|
+
typeof packageServices.workflowService === 'object') {
|
|
151
|
+
packageServices.workflowService = wrapWorkflowServiceForPackage(packageServices.workflowService, packageName, addonInstance?.namespace ?? null);
|
|
152
|
+
}
|
|
153
|
+
pikkuState(cacheKey, 'package', 'singletonServices', packageServices);
|
|
154
|
+
return packageServices;
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* Build the addon instance descriptor (namespace + per-instance overrides) for
|
|
158
|
+
* a bare intra-addon call, using the namespace currently executing on the wire.
|
|
159
|
+
* Returns undefined unless that namespace maps to the resolved package.
|
|
160
|
+
*/
|
|
161
|
+
export const addonInstanceForNamespace = (namespace, expectedPackage) => {
|
|
162
|
+
if (!namespace)
|
|
163
|
+
return undefined;
|
|
164
|
+
const cfg = pikkuState(null, 'addons', 'packages').get(namespace);
|
|
165
|
+
if (!cfg || cfg.package !== expectedPackage)
|
|
166
|
+
return undefined;
|
|
167
|
+
return {
|
|
168
|
+
namespace,
|
|
169
|
+
secretOverrides: cfg.secretOverrides,
|
|
170
|
+
variableOverrides: cfg.variableOverrides,
|
|
171
|
+
credentialOverrides: cfg.credentialOverrides,
|
|
172
|
+
};
|
|
173
|
+
};
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
export { PikkuRPCService, rpcService, RPCNotFoundError } from './rpc-runner.js';
|
|
1
|
+
export { PikkuRPCService, rpcService, RPCNotFoundError, RemoteAddonConfigError, RemoteAddonRequestError, } from './rpc-runner.js';
|
|
2
|
+
export { resolveRemoteAddonToken, RemoteAddonAuthError, } from './remote-addon-auth.js';
|
|
3
|
+
export type { RemoteAddonAuthBinding } from './remote-addon-auth.js';
|
|
2
4
|
export type { PikkuRPC, RPCMeta } from './rpc-types.js';
|
|
3
5
|
export { wireAddon } from './wire-addon.js';
|
|
4
6
|
export type { WireAddonConfig } from './wire-addon.js';
|
|
7
|
+
export { wireRemoteAddon } from './wire-remote-addon.js';
|
|
8
|
+
export type { WireRemoteAddonConfig, RemoteAddonAuth, } from './wire-remote-addon.js';
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
export { PikkuRPCService, rpcService, RPCNotFoundError } from './rpc-runner.js';
|
|
1
|
+
export { PikkuRPCService, rpcService, RPCNotFoundError, RemoteAddonConfigError, RemoteAddonRequestError, } from './rpc-runner.js';
|
|
2
|
+
export { resolveRemoteAddonToken, RemoteAddonAuthError, } from './remote-addon-auth.js';
|
|
2
3
|
export { wireAddon } from './wire-addon.js';
|
|
4
|
+
export { wireRemoteAddon } from './wire-remote-addon.js';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CoreServices, PikkuRawWire } from '../../types/core.types.js';
|
|
2
|
+
import { PikkuError } from '../../errors/error-handler.js';
|
|
3
|
+
/**
|
|
4
|
+
* How a `wireRemoteAddon` consumer supplies the token the hosted addon expects.
|
|
5
|
+
* This is a CLIENT authenticating to a hosted library — not the trusted mesh.
|
|
6
|
+
*/
|
|
7
|
+
export type RemoteAddonAuthBinding = {
|
|
8
|
+
credentialId: string;
|
|
9
|
+
} | {
|
|
10
|
+
secretId: string;
|
|
11
|
+
} | {
|
|
12
|
+
resolve: (services: CoreServices, wire: PikkuRawWire) => string | Promise<string>;
|
|
13
|
+
};
|
|
14
|
+
/** The addon's auth was bound but the source produced no token — fail closed. */
|
|
15
|
+
export declare class RemoteAddonAuthError extends PikkuError {
|
|
16
|
+
readonly namespace: string;
|
|
17
|
+
constructor(namespace: string, detail: string);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the bearer token for a remote addon call from the consumer's bound
|
|
21
|
+
* source. Returns `null` only when no auth is bound (a public surface).
|
|
22
|
+
*
|
|
23
|
+
* Security: the resolved token is never logged or traced; per-user credentials
|
|
24
|
+
* are read via the wire (scoped to `pikkuUserId`); an empty result fails closed.
|
|
25
|
+
*/
|
|
26
|
+
export declare function resolveRemoteAddonToken(auth: RemoteAddonAuthBinding | undefined, services: CoreServices, wire: PikkuRawWire, namespace: string): Promise<string | null>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { PikkuError, addError } from '../../errors/error-handler.js';
|
|
2
|
+
/** The addon's auth was bound but the source produced no token — fail closed. */
|
|
3
|
+
export class RemoteAddonAuthError extends PikkuError {
|
|
4
|
+
namespace;
|
|
5
|
+
constructor(namespace, detail) {
|
|
6
|
+
super(`Remote addon '${namespace}' auth could not be resolved: ${detail}`);
|
|
7
|
+
this.namespace = namespace;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
addError(RemoteAddonAuthError, {
|
|
11
|
+
status: 401,
|
|
12
|
+
message: 'Remote addon authentication could not be resolved.',
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* Resolve the bearer token for a remote addon call from the consumer's bound
|
|
16
|
+
* source. Returns `null` only when no auth is bound (a public surface).
|
|
17
|
+
*
|
|
18
|
+
* Security: the resolved token is never logged or traced; per-user credentials
|
|
19
|
+
* are read via the wire (scoped to `pikkuUserId`); an empty result fails closed.
|
|
20
|
+
*/
|
|
21
|
+
export async function resolveRemoteAddonToken(auth, services, wire, namespace) {
|
|
22
|
+
if (!auth) {
|
|
23
|
+
// No auth bound → the addon declares its remote surface public.
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
let token;
|
|
27
|
+
if ('credentialId' in auth) {
|
|
28
|
+
if (typeof wire.getCredential !== 'function') {
|
|
29
|
+
throw new RemoteAddonAuthError(namespace, `credentialId '${auth.credentialId}' requires a wire with credential access`);
|
|
30
|
+
}
|
|
31
|
+
token = await wire.getCredential(auth.credentialId);
|
|
32
|
+
}
|
|
33
|
+
else if ('secretId' in auth) {
|
|
34
|
+
token = await services.secrets.getSecret(auth.secretId);
|
|
35
|
+
}
|
|
36
|
+
else if (typeof auth.resolve === 'function') {
|
|
37
|
+
token = await auth.resolve(services, wire);
|
|
38
|
+
}
|
|
39
|
+
if (token === null || token === undefined || token === '') {
|
|
40
|
+
throw new RemoteAddonAuthError(namespace, 'resolved token was empty');
|
|
41
|
+
}
|
|
42
|
+
return String(token);
|
|
43
|
+
}
|
|
@@ -7,6 +7,15 @@ export declare class RPCNotFoundError extends PikkuError {
|
|
|
7
7
|
readonly rpcName: string;
|
|
8
8
|
constructor(rpcName: string);
|
|
9
9
|
}
|
|
10
|
+
/** A `wireRemoteAddon` namespace is missing a usable `serverUrl`. */
|
|
11
|
+
export declare class RemoteAddonConfigError extends PikkuError {
|
|
12
|
+
constructor(namespace: string, detail: string);
|
|
13
|
+
}
|
|
14
|
+
/** The hosted addon returned a non-2xx response for a remote RPC. */
|
|
15
|
+
export declare class RemoteAddonRequestError extends PikkuError {
|
|
16
|
+
readonly httpStatus: number;
|
|
17
|
+
constructor(namespace: string, fnName: string, status: number, detail: string);
|
|
18
|
+
}
|
|
10
19
|
import type { AIAgentInput } from '../ai-agent/ai-agent.types.js';
|
|
11
20
|
import type { StreamAIAgentOptions } from '../ai-agent/ai-agent-prepare.js';
|
|
12
21
|
/**
|
|
@@ -32,6 +41,15 @@ export declare class ContextAwareRPCService {
|
|
|
32
41
|
* @private
|
|
33
42
|
*/
|
|
34
43
|
private invokeAddonFunction;
|
|
44
|
+
/**
|
|
45
|
+
* Dispatch a `wireRemoteAddon` RPC over HTTP to the hosting service.
|
|
46
|
+
*
|
|
47
|
+
* The consumer sends the addon's own function name (not the namespaced form)
|
|
48
|
+
* to the host's `/remote/rpc/:rpcName` endpoint, authenticating as a client
|
|
49
|
+
* with the token bound in `wireRemoteAddon({ auth })`. The addon's handlers
|
|
50
|
+
* live on the host, so there is no local function meta to resolve.
|
|
51
|
+
*/
|
|
52
|
+
private invokeRemoteAddonFunction;
|
|
35
53
|
rpcWithWire<In = any, Out = any>(rpcName: string, data: In, wire: PikkuRawWire): Promise<Out>;
|
|
36
54
|
startWorkflow<In = any>(workflowName: string, input: In, options?: {
|
|
37
55
|
startNode?: string;
|
|
@@ -103,5 +121,6 @@ export declare const rpcService: PikkuRPCService<import("../../types/core.types.
|
|
|
103
121
|
logLevel?: import("../../services/logger.js").LogLevel;
|
|
104
122
|
secrets?: {};
|
|
105
123
|
workflow?: import("../workflow/workflow.types.js").WorkflowServiceConfig;
|
|
124
|
+
webhook?: import("../../services/webhook-service.js").WebhookServiceConfig;
|
|
106
125
|
postgres?: import("../../types/core.types.js").PostgresConfig;
|
|
107
126
|
}>, PikkuRPC>;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { runPikkuFunc } from '../../function/function-runner.js';
|
|
2
|
+
import { addonInstanceForNamespace } from './addon-runner.js';
|
|
2
3
|
import { pikkuState } from '../../pikku-state.js';
|
|
3
4
|
import { PikkuError, addError } from '../../errors/error-handler.js';
|
|
4
5
|
import { parseVersionedId } from '../../version.js';
|
|
6
|
+
import { resolveRemoteAddonToken } from './remote-addon-auth.js';
|
|
5
7
|
export class RPCNotFoundError extends PikkuError {
|
|
6
8
|
rpcName;
|
|
7
9
|
constructor(rpcName) {
|
|
@@ -14,8 +16,31 @@ addError(RPCNotFoundError, {
|
|
|
14
16
|
mcpCode: -32601,
|
|
15
17
|
message: 'RPC function not found.',
|
|
16
18
|
});
|
|
19
|
+
/** A `wireRemoteAddon` namespace is missing a usable `serverUrl`. */
|
|
20
|
+
export class RemoteAddonConfigError extends PikkuError {
|
|
21
|
+
constructor(namespace, detail) {
|
|
22
|
+
super(`Remote addon '${namespace}' is misconfigured: ${detail}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
addError(RemoteAddonConfigError, {
|
|
26
|
+
status: 500,
|
|
27
|
+
message: 'Remote addon is misconfigured.',
|
|
28
|
+
});
|
|
29
|
+
/** The hosted addon returned a non-2xx response for a remote RPC. */
|
|
30
|
+
export class RemoteAddonRequestError extends PikkuError {
|
|
31
|
+
httpStatus;
|
|
32
|
+
constructor(namespace, fnName, status, detail) {
|
|
33
|
+
super(`Remote addon '${namespace}:${fnName}' returned ${status}${detail ? `: ${detail}` : ''}`);
|
|
34
|
+
this.httpStatus = status;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
addError(RemoteAddonRequestError, {
|
|
38
|
+
status: 502,
|
|
39
|
+
message: 'Remote addon request failed.',
|
|
40
|
+
});
|
|
17
41
|
import { runAIAgent, resumeAIAgentSync } from '../ai-agent/ai-agent-runner.js';
|
|
18
42
|
import { streamAIAgent, resumeAIAgent } from '../ai-agent/ai-agent-stream.js';
|
|
43
|
+
import { wrapChannelWithAGUI } from '../ai-agent/ai-agent-agui.js';
|
|
19
44
|
/**
|
|
20
45
|
* Resolve a namespaced function reference to package and function names
|
|
21
46
|
* Uses pikkuState to look up the namespace -> package mapping
|
|
@@ -130,12 +155,16 @@ export class ContextAwareRPCService {
|
|
|
130
155
|
// 'namespace:func' boundary via invokeAddonFunction.
|
|
131
156
|
try {
|
|
132
157
|
const resolved = resolvePikkuFunction(funcName, this.packageName);
|
|
158
|
+
const addonInstance = resolved.packageName
|
|
159
|
+
? addonInstanceForNamespace(this.wire.addonNamespace, resolved.packageName)
|
|
160
|
+
: undefined;
|
|
133
161
|
return await runPikkuFunc('rpc', funcName, resolved.pikkuFuncId, {
|
|
134
162
|
auth: this.options.requiresAuth,
|
|
135
163
|
singletonServices: this.services,
|
|
136
164
|
data: () => data,
|
|
137
165
|
wire: updatedWire,
|
|
138
166
|
packageName: resolved.packageName,
|
|
167
|
+
addonInstance,
|
|
139
168
|
});
|
|
140
169
|
}
|
|
141
170
|
catch (e) {
|
|
@@ -163,6 +192,12 @@ export class ContextAwareRPCService {
|
|
|
163
192
|
if (!resolved) {
|
|
164
193
|
throw new RPCNotFoundError(namespacedFunction);
|
|
165
194
|
}
|
|
195
|
+
const namespace = namespacedFunction.slice(0, namespacedFunction.indexOf(':'));
|
|
196
|
+
// wireRemoteAddon: the addon ships as a devDependency (types only) and its
|
|
197
|
+
// handlers run on the host — dispatch over HTTP, not through local meta.
|
|
198
|
+
if (resolved.addonConfig?.remote) {
|
|
199
|
+
return this.invokeRemoteAddonFunction(namespace, resolved.function, data);
|
|
200
|
+
}
|
|
166
201
|
// Get the function meta from the addon package
|
|
167
202
|
// Addon packages use function meta, not RPC meta
|
|
168
203
|
const addonFunctionMeta = pikkuState(resolved.package, 'function', 'meta');
|
|
@@ -176,6 +211,14 @@ export class ContextAwareRPCService {
|
|
|
176
211
|
...(resolved.addonConfig?.tags ?? []),
|
|
177
212
|
...(funcMeta.tags ?? []),
|
|
178
213
|
];
|
|
214
|
+
// The namespace is the consumer-facing wireAddon name; it selects the
|
|
215
|
+
// per-instance singleton services and secret/variable/credential overrides.
|
|
216
|
+
const addonInstance = {
|
|
217
|
+
namespace,
|
|
218
|
+
secretOverrides: resolved.addonConfig?.secretOverrides,
|
|
219
|
+
variableOverrides: resolved.addonConfig?.variableOverrides,
|
|
220
|
+
credentialOverrides: resolved.addonConfig?.credentialOverrides,
|
|
221
|
+
};
|
|
179
222
|
// Execute the function using runPikkuFunc with the addon package's state
|
|
180
223
|
// We use the parent services (this.services) since addon packages share services
|
|
181
224
|
// Pass the function's tags so tag-based middleware/permissions are applied
|
|
@@ -186,7 +229,56 @@ export class ContextAwareRPCService {
|
|
|
186
229
|
wire,
|
|
187
230
|
packageName: resolved.package,
|
|
188
231
|
tags,
|
|
232
|
+
addonInstance,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Dispatch a `wireRemoteAddon` RPC over HTTP to the hosting service.
|
|
237
|
+
*
|
|
238
|
+
* The consumer sends the addon's own function name (not the namespaced form)
|
|
239
|
+
* to the host's `/remote/rpc/:rpcName` endpoint, authenticating as a client
|
|
240
|
+
* with the token bound in `wireRemoteAddon({ auth })`. The addon's handlers
|
|
241
|
+
* live on the host, so there is no local function meta to resolve.
|
|
242
|
+
*/
|
|
243
|
+
async invokeRemoteAddonFunction(namespace, fnName, data) {
|
|
244
|
+
const cfg = pikkuState(null, 'addons', 'packages').get(namespace);
|
|
245
|
+
if (!cfg?.remote) {
|
|
246
|
+
throw new RPCNotFoundError(`${namespace}:${fnName}`);
|
|
247
|
+
}
|
|
248
|
+
const serverUrl = typeof cfg.serverUrl === 'function'
|
|
249
|
+
? await cfg.serverUrl(this.services)
|
|
250
|
+
: cfg.serverUrl;
|
|
251
|
+
if (!serverUrl) {
|
|
252
|
+
throw new RemoteAddonConfigError(namespace, 'serverUrl resolved empty');
|
|
253
|
+
}
|
|
254
|
+
const remoteFn = cfg.remoteName ? cfg.remoteName(fnName) : fnName;
|
|
255
|
+
const token = await resolveRemoteAddonToken(cfg.remoteAuth, this.services, this.wire, namespace);
|
|
256
|
+
const headers = {
|
|
257
|
+
'content-type': 'application/json',
|
|
258
|
+
accept: 'application/json',
|
|
259
|
+
};
|
|
260
|
+
if (token) {
|
|
261
|
+
headers.authorization = `Bearer ${token}`;
|
|
262
|
+
}
|
|
263
|
+
if (this.wire.traceId) {
|
|
264
|
+
headers['x-trace-id'] = this.wire.traceId;
|
|
265
|
+
}
|
|
266
|
+
const base = serverUrl.replace(/\/+$/, '');
|
|
267
|
+
const res = await fetch(`${base}/remote/rpc/${encodeURIComponent(remoteFn)}`, {
|
|
268
|
+
method: 'POST',
|
|
269
|
+
headers,
|
|
270
|
+
body: JSON.stringify({ rpcName: remoteFn, data }),
|
|
189
271
|
});
|
|
272
|
+
if (!res.ok) {
|
|
273
|
+
// Best-effort body read to enrich the thrown error (mirrors postRpc).
|
|
274
|
+
const detail = (await res.text().catch(() => '')).slice(0, 300);
|
|
275
|
+
throw new RemoteAddonRequestError(namespace, remoteFn, res.status, detail);
|
|
276
|
+
}
|
|
277
|
+
if (res.status === 204) {
|
|
278
|
+
return undefined;
|
|
279
|
+
}
|
|
280
|
+
const text = await res.text();
|
|
281
|
+
return (text ? JSON.parse(text) : undefined);
|
|
190
282
|
}
|
|
191
283
|
async rpcWithWire(rpcName, data, wire) {
|
|
192
284
|
const mergedWire = {
|
|
@@ -198,12 +290,16 @@ export class ContextAwareRPCService {
|
|
|
198
290
|
}
|
|
199
291
|
try {
|
|
200
292
|
const resolved = resolvePikkuFunction(rpcName, this.packageName);
|
|
293
|
+
const addonInstance = resolved.packageName
|
|
294
|
+
? addonInstanceForNamespace(this.wire.addonNamespace, resolved.packageName)
|
|
295
|
+
: undefined;
|
|
201
296
|
return await runPikkuFunc('rpc', rpcName, resolved.pikkuFuncId, {
|
|
202
297
|
auth: this.options.requiresAuth,
|
|
203
298
|
singletonServices: this.services,
|
|
204
299
|
data: () => data,
|
|
205
300
|
wire: mergedWire,
|
|
206
301
|
packageName: resolved.packageName,
|
|
302
|
+
addonInstance,
|
|
207
303
|
});
|
|
208
304
|
}
|
|
209
305
|
catch (e) {
|
|
@@ -250,16 +346,26 @@ export class ContextAwareRPCService {
|
|
|
250
346
|
const channel = this.wire.channel;
|
|
251
347
|
if (!channel)
|
|
252
348
|
throw new Error('No channel available for streaming');
|
|
253
|
-
|
|
349
|
+
let currentRunId;
|
|
350
|
+
await streamAIAgent(agentName, input, wrapChannelWithAGUI(channel, {
|
|
351
|
+
threadId: input.threadId,
|
|
352
|
+
getRunId: () => currentRunId,
|
|
353
|
+
}), {
|
|
254
354
|
sessionService: this.options.sessionService,
|
|
255
355
|
getCredential: this.wire.getCredential?.bind(this.wire),
|
|
256
|
-
}, undefined,
|
|
356
|
+
}, undefined, {
|
|
357
|
+
...options,
|
|
358
|
+
onRunCreated: (runId) => {
|
|
359
|
+
currentRunId = runId;
|
|
360
|
+
options?.onRunCreated?.(runId);
|
|
361
|
+
},
|
|
362
|
+
});
|
|
257
363
|
},
|
|
258
364
|
resume: async (runId, input, options) => {
|
|
259
365
|
const channel = this.wire.channel;
|
|
260
366
|
if (!channel)
|
|
261
367
|
throw new Error('No channel available for streaming');
|
|
262
|
-
await resumeAIAgent({ runId, ...input }, channel, {
|
|
368
|
+
await resumeAIAgent({ runId, ...input }, wrapChannelWithAGUI(channel, { runId }), {
|
|
263
369
|
sessionService: this.options.sessionService,
|
|
264
370
|
getCredential: this.wire.getCredential?.bind(this.wire),
|
|
265
371
|
}, options);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type PikkuRPC<Invoke extends
|
|
1
|
+
export type PikkuRPC<Invoke extends (...args: any[]) => any = (...args: any[]) => any, Remote extends (...args: any[]) => any = (...args: any[]) => any, startWorkflow extends (...args: any[]) => any = (...args: any[]) => any, AgentRun extends (...args: any[]) => any = (...args: any[]) => any, AgentStream extends (...args: any[]) => any = (...args: any[]) => any> = {
|
|
2
2
|
depth: number;
|
|
3
3
|
global: boolean;
|
|
4
4
|
invoke: Invoke;
|
|
@@ -41,5 +41,19 @@ export interface ResolvedFunction {
|
|
|
41
41
|
auth?: boolean;
|
|
42
42
|
tags?: string[];
|
|
43
43
|
rpcEndpoint?: string;
|
|
44
|
+
secretOverrides?: Record<string, string>;
|
|
45
|
+
variableOverrides?: Record<string, string>;
|
|
46
|
+
credentialOverrides?: Record<string, string>;
|
|
47
|
+
/** Set by `wireRemoteAddon`: dispatch this namespace's RPCs over HTTP */
|
|
48
|
+
remote?: boolean;
|
|
49
|
+
serverUrl?: string | ((services: any) => string | Promise<string>);
|
|
50
|
+
remoteAuth?: {
|
|
51
|
+
credentialId: string;
|
|
52
|
+
} | {
|
|
53
|
+
secretId: string;
|
|
54
|
+
} | {
|
|
55
|
+
resolve: (services: any, wire: any) => string | Promise<string>;
|
|
56
|
+
};
|
|
57
|
+
remoteName?: (fn: string) => string;
|
|
44
58
|
};
|
|
45
59
|
}
|
|
@@ -5,5 +5,14 @@ export const wireAddon = (config) => {
|
|
|
5
5
|
rpcEndpoint: config.rpcEndpoint,
|
|
6
6
|
auth: config.auth,
|
|
7
7
|
tags: config.tags,
|
|
8
|
+
...(config.secretOverrides
|
|
9
|
+
? { secretOverrides: config.secretOverrides }
|
|
10
|
+
: {}),
|
|
11
|
+
...(config.variableOverrides
|
|
12
|
+
? { variableOverrides: config.variableOverrides }
|
|
13
|
+
: {}),
|
|
14
|
+
...(config.credentialOverrides
|
|
15
|
+
? { credentialOverrides: config.credentialOverrides }
|
|
16
|
+
: {}),
|
|
8
17
|
});
|
|
9
18
|
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { CoreServices, PikkuWire } from '../../types/core.types.js';
|
|
2
|
+
/**
|
|
3
|
+
* How the consumer supplies the token the hosted addon's auth expects.
|
|
4
|
+
*
|
|
5
|
+
* This is a CLIENT authenticating to a hosted library — NOT pikku's trusted
|
|
6
|
+
* machine-to-machine mesh (which uses `PIKKU_REMOTE_SECRET`). The consumer binds
|
|
7
|
+
* the addon's declared auth requirement to a local source:
|
|
8
|
+
* - `credentialId` — per-user credential, resolved via `wire.getCredential(id)`
|
|
9
|
+
* - `secretId` — platform key, resolved via the secrets service
|
|
10
|
+
* - `resolve` — custom escape hatch
|
|
11
|
+
* Omit entirely when the addon declares its remote surface is public.
|
|
12
|
+
*
|
|
13
|
+
* The value is sent `Authorization: Bearer <token>` by default; a non-default
|
|
14
|
+
* header is declared once by the addon (its meta), never chosen here.
|
|
15
|
+
*/
|
|
16
|
+
export type RemoteAddonAuth = {
|
|
17
|
+
credentialId: string;
|
|
18
|
+
} | {
|
|
19
|
+
secretId: string;
|
|
20
|
+
} | {
|
|
21
|
+
resolve: (services: CoreServices, wire: PikkuWire) => string | Promise<string>;
|
|
22
|
+
};
|
|
23
|
+
export type WireRemoteAddonConfig = {
|
|
24
|
+
/** Consumer-facing namespace, e.g. `registry` → `rpc('registry:getOpenApi')` */
|
|
25
|
+
name: string;
|
|
26
|
+
/**
|
|
27
|
+
* The addon package. Installed as a **devDependency** (types only — its
|
|
28
|
+
* handlers run on the host); `pikku verify` enforces this.
|
|
29
|
+
*/
|
|
30
|
+
package: string;
|
|
31
|
+
/** Base URL of the host serving the addon's remote surface. */
|
|
32
|
+
serverUrl: string | ((services: CoreServices) => string | Promise<string>);
|
|
33
|
+
/** Bind the addon's declared auth to a local source. Omit if the surface is public. */
|
|
34
|
+
auth?: RemoteAddonAuth;
|
|
35
|
+
/** Map a consumer-facing fn name → the remote fn name, when they differ (rare). */
|
|
36
|
+
remoteName?: (fn: string) => string;
|
|
37
|
+
tags?: string[];
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Consume a hosted addon's `remote: true` RPCs transparently over HTTP.
|
|
41
|
+
*
|
|
42
|
+
* Unlike `wireAddon` (which bundles the addon's functions in-process, a
|
|
43
|
+
* production dependency), `wireRemoteAddon` dispatches `rpc('name:fn')` to the
|
|
44
|
+
* host at `serverUrl`, fully typed — the addon ships as a devDependency (types
|
|
45
|
+
* only). See {@link RemoteAddonAuth} for how auth is bound.
|
|
46
|
+
*/
|
|
47
|
+
export declare const wireRemoteAddon: (config: WireRemoteAddonConfig) => void;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { pikkuState } from '../../pikku-state.js';
|
|
2
|
+
/**
|
|
3
|
+
* Consume a hosted addon's `remote: true` RPCs transparently over HTTP.
|
|
4
|
+
*
|
|
5
|
+
* Unlike `wireAddon` (which bundles the addon's functions in-process, a
|
|
6
|
+
* production dependency), `wireRemoteAddon` dispatches `rpc('name:fn')` to the
|
|
7
|
+
* host at `serverUrl`, fully typed — the addon ships as a devDependency (types
|
|
8
|
+
* only). See {@link RemoteAddonAuth} for how auth is bound.
|
|
9
|
+
*/
|
|
10
|
+
export const wireRemoteAddon = (config) => {
|
|
11
|
+
pikkuState(null, 'addons', 'packages').set(config.name, {
|
|
12
|
+
package: config.package,
|
|
13
|
+
tags: config.tags,
|
|
14
|
+
remote: true,
|
|
15
|
+
serverUrl: config.serverUrl,
|
|
16
|
+
...(config.auth ? { remoteAuth: config.auth } : {}),
|
|
17
|
+
...(config.remoteName ? { remoteName: config.remoteName } : {}),
|
|
18
|
+
});
|
|
19
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { wireScope } from './wire-scope.js';
|
|
2
|
+
export { flattenScopeDefinitions, validateAndBuildScopeDefinitionsMeta, } from './validate-scope-definitions.js';
|
|
3
|
+
export type { CoreScopes, CoreScopeNode, FlatScope, ScopeDefinitionMeta, ScopeDefinitionsMeta, ScopeDefinitions, ScopeNodeMeta, } from './scope.types.js';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A node within a scope tree. Every node is itself a grantable scope — given
|
|
3
|
+
* `admin` containing `invoices` containing `create`, all three of `admin`,
|
|
4
|
+
* `admin:invoices` and `admin:invoices:create` are valid scope ids.
|
|
5
|
+
*/
|
|
6
|
+
export type CoreScopeNode = {
|
|
7
|
+
/** Short human-readable name, e.g. "Administration". */
|
|
8
|
+
displayName?: string;
|
|
9
|
+
/** Longer-form description, surfaced in the console when granting. */
|
|
10
|
+
description?: string;
|
|
11
|
+
/** Nested scopes, keyed by their segment. */
|
|
12
|
+
scopes?: Record<string, CoreScopeNode>;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Scope trees to declare, keyed by their root segment. A root is just a node —
|
|
16
|
+
* it is named by its key, exactly like every node beneath it. A key must not
|
|
17
|
+
* contain `:` or be `*`.
|
|
18
|
+
*/
|
|
19
|
+
export type CoreScopes = Record<string, CoreScopeNode>;
|
|
20
|
+
export type ScopeNodeMeta = {
|
|
21
|
+
displayName?: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
scopes?: Record<string, ScopeNodeMeta>;
|
|
24
|
+
};
|
|
25
|
+
export type ScopeDefinitionMeta = {
|
|
26
|
+
name: string;
|
|
27
|
+
displayName?: string;
|
|
28
|
+
description?: string;
|
|
29
|
+
scopes?: Record<string, ScopeNodeMeta>;
|
|
30
|
+
sourceFile?: string;
|
|
31
|
+
};
|
|
32
|
+
export type ScopeDefinitions = ScopeDefinitionMeta[];
|
|
33
|
+
export type ScopeDefinitionsMeta = Record<string, ScopeDefinitionMeta>;
|
|
34
|
+
/** A single scope, flattened out of the declared tree. */
|
|
35
|
+
export type FlatScope = {
|
|
36
|
+
/** Colon-delimited id, e.g. `admin:invoices:create`. */
|
|
37
|
+
id: string;
|
|
38
|
+
description?: string;
|
|
39
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|