@pikku/core 0.12.145 → 0.12.147

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.
Files changed (44) hide show
  1. package/dist/crypto-utils.d.ts +3 -3
  2. package/dist/crypto-utils.js +3 -3
  3. package/dist/function/function-meta.types.d.ts +1 -1
  4. package/dist/function/function-runner.js +7 -0
  5. package/dist/function/functions.types.d.ts +8 -0
  6. package/dist/{remote.d.ts → mesh.d.ts} +2 -2
  7. package/dist/{remote.js → mesh.js} +12 -12
  8. package/dist/middleware/index.d.ts +2 -2
  9. package/dist/middleware/index.js +2 -2
  10. package/dist/middleware/{remote-auth.d.ts → mesh-auth.d.ts} +6 -6
  11. package/dist/middleware/{remote-auth.js → mesh-auth.js} +20 -20
  12. package/dist/types/core.types.d.ts +3 -3
  13. package/dist/types/state.types.d.ts +12 -7
  14. package/dist/wirings/addon/addon-reach.d.ts +10 -0
  15. package/dist/wirings/addon/addon-reach.js +29 -0
  16. package/dist/wirings/addon/index.d.ts +2 -2
  17. package/dist/wirings/addon/index.js +1 -1
  18. package/dist/wirings/addon/pikku-studio.d.ts +60 -0
  19. package/dist/wirings/addon/pikku-studio.js +7 -0
  20. package/dist/wirings/addon/remote-addon-auth.d.ts +10 -2
  21. package/dist/wirings/addon/remote-addon-auth.js +6 -1
  22. package/dist/wirings/addon/remote-addon-package.d.ts +13 -0
  23. package/dist/wirings/addon/remote-addon-package.js +21 -0
  24. package/dist/wirings/addon/wire-addon.d.ts +17 -13
  25. package/dist/wirings/addon/wire-addon.js +6 -6
  26. package/dist/wirings/addon/wire-remote-addon.d.ts +37 -3
  27. package/dist/wirings/addon/wire-remote-addon.js +18 -1
  28. package/dist/wirings/agent/agent-rpc.js +6 -3
  29. package/dist/wirings/channel/channel-rpc-service.d.ts +1 -1
  30. package/dist/wirings/channel/channel-rpc-service.js +1 -1
  31. package/dist/wirings/rpc/index.d.ts +1 -1
  32. package/dist/wirings/rpc/index.js +1 -1
  33. package/dist/wirings/rpc/rpc-runner.d.ts +15 -9
  34. package/dist/wirings/rpc/rpc-runner.js +215 -49
  35. package/dist/wirings/rpc/rpc-types.d.ts +8 -5
  36. package/dist/wirings/workflow/graph/graph-runner.js +2 -2
  37. package/dist/wirings/workflow/pikku-workflow-service.js +3 -4
  38. package/dist/wirings/workflow/workflow-compensation.js +2 -2
  39. package/dist/wirings/workflow/workflow-meta-resolver.d.ts +5 -0
  40. package/dist/wirings/workflow/workflow-meta-resolver.js +5 -0
  41. package/dist/wirings/workflow/workflow-queue-routing.js +2 -2
  42. package/package.json +3 -2
  43. package/dist/wirings/addon/define-screens.d.ts +0 -34
  44. package/dist/wirings/addon/define-screens.js +0 -5
@@ -23,10 +23,10 @@ export type WireAddonConfig = {
23
23
  mcp?: boolean | string[];
24
24
  /**
25
25
  * Which of the addon's functions `rpc.exposed` may call, under
26
- * `<name>:<function>`. Unset or `true` keeps the functions the addon itself
27
- * declared `expose: true`; `false` exposes none of them; a list names exactly
28
- * the functions to expose, whether or not the addon declared them, and is
29
- * typed against the addon's function names.
26
+ * `<name>:<function>`. Only functions the addon itself declared
27
+ * `expose: true` can ever be exposed: `true` exposes all of them, a list
28
+ * only those it names, and unset or `false` none. A listed function the
29
+ * addon did not declare stays unexposed.
30
30
  *
31
31
  * knowledge: decisions/security/wire-addon-expose-selects-the-rpc-surface.md
32
32
  */
@@ -46,18 +46,22 @@ export type WireAddonConfig = {
46
46
  /** Filters this addon in and out of a build — see the `tags` option on `pikku all`. It has no effect at runtime. */
47
47
  tags?: string[];
48
48
  /**
49
- * Mounts the screens the addon ships (see `defineScreens`) in the host under
50
- * `/addons/<name>`. Without it the addon's screens are ignored. The package
51
- * must declare at least one, or `pikku all` rejects the wiring.
49
+ * Mounts the screens the addon ships (see `pikkuStudio`) in the host, each at
50
+ * the path its `scope` gives it. Without it the addon's screens are ignored.
51
+ * The package must declare at least one, or `pikku all` rejects the wiring.
52
52
  */
53
53
  ui?: boolean;
54
54
  /** Required of every function in the addon, on top of the function's own. */
55
55
  scopes?: string[];
56
56
  /**
57
- * The other addons this one may call, as `{ '<package>': '<name>' }`: the
58
- * package it was written against, and the name that package is wired under
59
- * in this app. The addon calls `'<package>:<function>'`; anything it did not
60
- * list is refused, whatever else the app has wired.
57
+ * The other addons this one may call, as `{ '<alias>': '<name>' }`: the name
58
+ * the addon calls another by, and the name that addon is wired under in this
59
+ * app — by `wireAddon` or `wireRemoteAddon`, either serves. The alias is the
60
+ * one the addon's package.json declares under `pikku.uses`
61
+ * (`{ "stripe": "@acme/addon-stripe@^1.0.0" }`), or, for an addon that lists
62
+ * plain names, the package name itself. The addon calls
63
+ * `'<alias>:<function>'`; anything it did not declare, or this app did not
64
+ * map, is refused, whatever else the app has wired.
61
65
  */
62
66
  uses?: Record<string, string>;
63
67
  /** Points a secret the addon reads at a different key in this deployment, so two instances can hold different credentials. */
@@ -106,8 +110,8 @@ export type WireAddonConfig = {
106
110
  export declare const wireAddon: (config: WireAddonConfig) => void;
107
111
  /**
108
112
  * Whether `rpc.exposed` may reach an addon function through the instance that
109
- * resolved it. The wiring's `expose` decides when it is `false` or a list;
110
- * otherwise the addon's own `expose: true` does.
113
+ * resolved it. Both sides must agree: the addon declares the function
114
+ * `expose: true`, and the wiring opts in with `true` or a list naming it.
111
115
  *
112
116
  * knowledge: decisions/security/wire-addon-expose-selects-the-rpc-surface.md
113
117
  */
@@ -39,19 +39,19 @@ export const wireAddon = (config) => {
39
39
  };
40
40
  /**
41
41
  * Whether `rpc.exposed` may reach an addon function through the instance that
42
- * resolved it. The wiring's `expose` decides when it is `false` or a list;
43
- * otherwise the addon's own `expose: true` does.
42
+ * resolved it. Both sides must agree: the addon declares the function
43
+ * `expose: true`, and the wiring opts in with `true` or a list naming it.
44
44
  *
45
45
  * knowledge: decisions/security/wire-addon-expose-selects-the-rpc-surface.md
46
46
  */
47
47
  export const isAddonFunctionExposed = (expose, functionName, declaredExpose) => {
48
+ if (declaredExpose !== true) {
49
+ return false;
50
+ }
48
51
  if (Array.isArray(expose)) {
49
52
  return expose.includes(functionName);
50
53
  }
51
- if (expose === false) {
52
- return false;
53
- }
54
- return declaredExpose === true;
54
+ return expose === true;
55
55
  };
56
56
  /**
57
57
  * The addon configs a running function is governed by: the named instance when
@@ -5,22 +5,56 @@ export type RemoteAddonAuth = {
5
5
  } | {
6
6
  secretId: string;
7
7
  } | {
8
- resolve: (services: CoreServices, wire: PikkuWire) => string | Promise<string>;
8
+ /** `null` sends this call without a credential, for a public surface. */
9
+ resolve: (services: CoreServices, wire: PikkuWire) => string | null | Promise<string | null>;
9
10
  };
11
+ /**
12
+ * Sent on every call to a hosted addon. The host's `rpc.exposed` then answers
13
+ * only functions in the addon's remote contract (`remote: true`), so a consumer
14
+ * holding a token cannot reach the host's other exposed functions through it.
15
+ * It can only narrow what the host answers, never widen it.
16
+ *
17
+ * knowledge: decisions/security/wire-addon-expose-selects-the-rpc-surface.md
18
+ */
19
+ export declare const REMOTE_CONTRACT_HEADER = "x-pikku-remote-contract";
20
+ /** How long a call to a hosted addon may take when `timeoutMs` is not set. */
21
+ export declare const DEFAULT_REMOTE_ADDON_TIMEOUT_MS = 30000;
10
22
  /** Options for `wireRemoteAddon`: the namespace, the contract package, where the addon is deployed and how calls authenticate. */
11
23
  export type WireRemoteAddonConfig = {
12
24
  /** Consumer-facing namespace, e.g. `registry` → `rpc('registry:getOpenApi')` */
13
25
  name: string;
14
- /** Must be installed as a devDependency — `pikku verify` enforces this. */
26
+ /**
27
+ * The contract package, as a string literal. `@scope/name@^1.4.0` pins a
28
+ * range: `pikku all` downloads the addon's SDK from the registry and
29
+ * records it in `pikku-addons.lock`. A bare `@scope/name` must be installed
30
+ * as a devDependency instead — `pikku verify` enforces both.
31
+ */
15
32
  package: string;
16
33
  /** Where the addon is deployed, e.g. `https://registry.example.com`. A function when it varies per environment. */
17
34
  serverUrl: string | ((services: CoreServices) => string | Promise<string>);
18
35
  /** Omit when the addon declares its remote surface public. */
19
36
  auth?: RemoteAddonAuth;
20
- /** Map a consumer-facing fn name → the remote fn name, when they differ (rare). */
37
+ /**
38
+ * Map a consumer-facing fn name → the remote fn name, when they differ (rare).
39
+ * It gets the base name; a versioned call appends `@vN` to what it returns.
40
+ */
21
41
  remoteName?: (fn: string) => string;
22
42
  /** Applied to every function the addon contributes, so tag middleware and permissions reach them. */
23
43
  tags?: string[];
44
+ /**
45
+ * Which of the addon's contract functions this app's `rpc.exposed` — and so
46
+ * its public `POST /rpc/:rpcName` — forwards to the host, under
47
+ * `<name>:<function>`. `true` forwards every contract function, a list only
48
+ * those named, and unset or `false` none: server-side `rpc('<name>:fn')`
49
+ * calls are unaffected.
50
+ *
51
+ * knowledge: decisions/security/wire-addon-expose-selects-the-rpc-surface.md
52
+ */
53
+ expose?: boolean | string[];
54
+ /** Milliseconds a call may take before it is aborted. Defaults to {@link DEFAULT_REMOTE_ADDON_TIMEOUT_MS}. */
55
+ timeoutMs?: number;
56
+ /** Extra headers for every call to the host. Cannot replace the auth, trace or contract headers. */
57
+ headers?: (services: CoreServices, wire: PikkuWire) => Record<string, string> | Promise<Record<string, string>>;
24
58
  };
25
59
  /**
26
60
  * Installs an addon that runs as its own deployed service: the contract is
@@ -1,15 +1,32 @@
1
1
  import { pikkuState } from '../../pikku-state.js';
2
+ import { parseRemoteAddonPackage } from './remote-addon-package.js';
3
+ /**
4
+ * Sent on every call to a hosted addon. The host's `rpc.exposed` then answers
5
+ * only functions in the addon's remote contract (`remote: true`), so a consumer
6
+ * holding a token cannot reach the host's other exposed functions through it.
7
+ * It can only narrow what the host answers, never widen it.
8
+ *
9
+ * knowledge: decisions/security/wire-addon-expose-selects-the-rpc-surface.md
10
+ */
11
+ export const REMOTE_CONTRACT_HEADER = 'x-pikku-remote-contract';
12
+ /** How long a call to a hosted addon may take when `timeoutMs` is not set. */
13
+ export const DEFAULT_REMOTE_ADDON_TIMEOUT_MS = 30_000;
2
14
  /**
3
15
  * Installs an addon that runs as its own deployed service: the contract is
4
16
  * local, the calls go over the wire to the addon's own host.
5
17
  */
6
18
  export const wireRemoteAddon = (config) => {
7
19
  pikkuState(null, 'addons', 'packages').set(config.name, {
8
- package: config.package,
20
+ package: parseRemoteAddonPackage(config.package).name,
9
21
  tags: config.tags,
10
22
  remote: true,
11
23
  serverUrl: config.serverUrl,
24
+ ...(config.expose !== undefined ? { expose: config.expose } : {}),
12
25
  ...(config.auth ? { remoteAuth: config.auth } : {}),
13
26
  ...(config.remoteName ? { remoteName: config.remoteName } : {}),
27
+ ...(config.timeoutMs !== undefined
28
+ ? { remoteTimeoutMs: config.timeoutMs }
29
+ : {}),
30
+ ...(config.headers ? { remoteHeaders: config.headers } : {}),
14
31
  });
15
32
  };
@@ -1,4 +1,5 @@
1
1
  import { pikkuState } from '../../pikku-state.js';
2
+ import { mapAddonCall } from '../addon/addon-reach.js';
2
3
  import { runAgent, resumeAgentSync } from './agent-runner.js';
3
4
  import { streamAgent, resumeAgent, interruptAgent } from './agent-stream.js';
4
5
  import { wrapChannelWithAGUI } from './agent-agui.js';
@@ -31,10 +32,10 @@ export const createAgentRPC = (wire, options) => {
31
32
  }),
32
33
  });
33
34
  return {
34
- run: async (agentName, input) => asRunResult(await runAgent(agentName, input, params())),
35
+ run: async (agentName, input) => asRunResult(await runAgent(mapAddonCall(wire.addonNamespace, agentName), input, params())),
35
36
  stream: async (agentName, input, streamOptions) => {
36
37
  let currentRunId;
37
- await streamAgent(agentName, input, wrapChannelWithAGUI(streamingChannel(), {
38
+ await streamAgent(mapAddonCall(wire.addonNamespace, agentName), input, wrapChannelWithAGUI(streamingChannel(), {
38
39
  threadId: input.threadId,
39
40
  getRunId: () => currentRunId,
40
41
  }), params(), undefined, {
@@ -49,7 +50,9 @@ export const createAgentRPC = (wire, options) => {
49
50
  await resumeAgent({ runId, ...input }, wrapChannelWithAGUI(streamingChannel(), { runId }), params(), streamOptions);
50
51
  },
51
52
  interrupt: async (runId, reason) => interruptAgent({ runId, ...(reason ? { reason } : {}) }, { sessionService: options.sessionService }),
52
- approve: async (runId, approvals, expectedAgentName) => asRunResult(await resumeAgentSync(runId, approvals, { sessionService: options.sessionService }, expectedAgentName)),
53
+ approve: async (runId, approvals, expectedAgentName) => asRunResult(await resumeAgentSync(runId, approvals, { sessionService: options.sessionService }, expectedAgentName === undefined
54
+ ? undefined
55
+ : mapAddonCall(wire.addonNamespace, expectedAgentName))),
53
56
  };
54
57
  };
55
58
  /**
@@ -7,7 +7,7 @@ import { type ChannelRPCValidator } from './channel-rpc.types.js';
7
7
  * inbound route of its own.
8
8
  *
9
9
  * It slots into the existing `services.deploymentService` hole, leaving
10
- * `rpc.remote` resolution, session propagation and traceId plumbing unchanged.
10
+ * `rpc.mesh` resolution, session propagation and traceId plumbing unchanged.
11
11
  */
12
12
  export declare class ChannelDeploymentService implements DeploymentService {
13
13
  private readonly send;
@@ -6,7 +6,7 @@ import { CHANNEL_RPC_REQUEST, CHANNEL_RPC_RESPONSE, ChannelRPCError, isChannelRP
6
6
  * inbound route of its own.
7
7
  *
8
8
  * It slots into the existing `services.deploymentService` hole, leaving
9
- * `rpc.remote` resolution, session propagation and traceId plumbing unchanged.
9
+ * `rpc.mesh` resolution, session propagation and traceId plumbing unchanged.
10
10
  */
11
11
  export class ChannelDeploymentService {
12
12
  send;
@@ -1,2 +1,2 @@
1
- export { PikkuRPCService, rpcService, RPCNotFoundError, RemoteAddonConfigError, RemoteAddonRequestError, } from './rpc-runner.js';
1
+ export { PikkuRPCService, rpcService, RPCNotFoundError, RemoteAddonConfigError, RemoteAddonFailedError, RemoteAddonTimeoutError, } from './rpc-runner.js';
2
2
  export type { PikkuRPC, RPCMeta } from './rpc-types.js';
@@ -1 +1 @@
1
- export { PikkuRPCService, rpcService, RPCNotFoundError, RemoteAddonConfigError, RemoteAddonRequestError, } from './rpc-runner.js';
1
+ export { PikkuRPCService, rpcService, RPCNotFoundError, RemoteAddonConfigError, RemoteAddonFailedError, RemoteAddonTimeoutError, } from './rpc-runner.js';
@@ -1,3 +1,4 @@
1
+ import { AddonNotUsedError } from '../addon/addon-reach.js';
1
2
  import type { CoreServices, PikkuRawWire } from '../../types/core.types.js';
2
3
  import type { SessionService } from '../../services/user-session-service.js';
3
4
  import type { CoreUserSession } from '../../types/core.types.js';
@@ -11,20 +12,25 @@ import type { PikkuRPC, ResolvedFunction } from './rpc-types.js';
11
12
  */
12
13
  export declare class RPCNotFoundError extends PikkuError {
13
14
  readonly rpcName: string;
14
- constructor(rpcName: string);
15
+ constructor(rpcName: string, message?: string);
15
16
  }
16
17
  /** Thrown when a remote addon's configuration cannot be used, for example when its `serverUrl` resolves empty. Maps to HTTP 500. */
17
18
  export declare class RemoteAddonConfigError extends PikkuError {
18
19
  constructor(namespace: string, detail: string);
19
20
  }
20
- /** Thrown when a remote addon answers an RPC with a non-2xx status. Maps to HTTP 502; the upstream status is kept on `httpStatus` and the first 300 characters of its body in the message. */
21
- export declare class RemoteAddonRequestError extends PikkuError {
22
- readonly httpStatus: number;
23
- constructor(namespace: string, fnName: string, status: number, detail: string);
21
+ /**
22
+ * Thrown when a remote addon fails on its own side — a 5xx, an unreachable
23
+ * host, or a response that is not JSON. Maps to HTTP 502. What went wrong is
24
+ * logged on this host; none of it reaches the caller.
25
+ */
26
+ export declare class RemoteAddonFailedError extends PikkuError {
27
+ constructor(namespace: string, fnName: string);
24
28
  }
25
- export declare class AddonNotUsedError extends PikkuError {
26
- constructor(caller: string, target: string);
29
+ /** Thrown when a remote addon does not answer within the wiring's `timeoutMs`. Maps to HTTP 504. */
30
+ export declare class RemoteAddonTimeoutError extends PikkuError {
31
+ constructor(namespace: string, fnName: string, timeoutMs: number);
27
32
  }
33
+ export { AddonNotUsedError };
28
34
  export declare const resolveNamespace: (namespacedFunction: string) => ResolvedFunction | null;
29
35
  export declare class ContextAwareRPCService {
30
36
  private services;
@@ -72,7 +78,7 @@ export declare class ContextAwareRPCService {
72
78
  * the tree behind it.
73
79
  */
74
80
  get agent(): PikkuRPC['agent'];
75
- remote<In = any, Out = any>(funcName: string, data: In): Promise<Out>;
81
+ mesh<In = any, Out = any>(funcName: string, data: In): Promise<Out>;
76
82
  }
77
83
  /**
78
84
  * Builds the `rpc` object handed to every function on its wire.
@@ -81,7 +87,7 @@ export declare class ContextAwareRPCService {
81
87
  * `DeploymentService` when the function lives in another deployment unit.
82
88
  * `rpc.exposed` does the same but only reaches functions marked `expose: true`
83
89
  * (and, for addons, those the consuming app exposes), which is what the public
84
- * RPC endpoint uses. `rpc.remote` always goes through the `DeploymentService`.
90
+ * RPC endpoint uses. `rpc.mesh` always goes through the `DeploymentService`.
85
91
  * Calls carry the caller's wire, so the session and trace ID propagate. An addon
86
92
  * can only call the addons it lists in `uses`.
87
93
  *