@peers-app/peers-sdk 0.23.10 → 0.23.11

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 (31) hide show
  1. package/dist/contracts/__tests__/builder.test.js +25 -0
  2. package/dist/contracts/__tests__/contract-provider-router.test.js +45 -0
  3. package/dist/contracts/__tests__/contract-proxy.test.js +67 -0
  4. package/dist/contracts/__tests__/contract-resolvers.test.js +133 -0
  5. package/dist/contracts/__tests__/remote-contract-transport.test.d.ts +1 -0
  6. package/dist/contracts/__tests__/remote-contract-transport.test.js +89 -0
  7. package/dist/contracts/builder.d.ts +47 -1
  8. package/dist/contracts/builder.js +80 -22
  9. package/dist/contracts/contract-provider-router.d.ts +6 -0
  10. package/dist/contracts/contract-provider-router.js +25 -3
  11. package/dist/contracts/contract-proxy.d.ts +41 -1
  12. package/dist/contracts/contract-proxy.js +103 -0
  13. package/dist/contracts/contract-resolvers.d.ts +47 -13
  14. package/dist/contracts/contract-resolvers.js +125 -35
  15. package/dist/contracts/extract.d.ts +4 -1
  16. package/dist/contracts/extract.js +3 -1
  17. package/dist/contracts/index.d.ts +4 -3
  18. package/dist/contracts/index.js +10 -2
  19. package/dist/contracts/remote-contract-transport.d.ts +69 -0
  20. package/dist/contracts/remote-contract-transport.js +95 -0
  21. package/dist/contracts/types.d.ts +57 -0
  22. package/dist/data/tools.d.ts +4 -0
  23. package/dist/data/tools.js +7 -0
  24. package/dist/package-loader/isolated-artifact.js +16 -1
  25. package/dist/package-loader/isolated-runtime.d.ts +5 -2
  26. package/dist/package-loader/package-loader.d.ts +15 -1
  27. package/dist/package-loader/package-loader.js +48 -0
  28. package/dist/package-loader/resolve-consumed-contracts.d.ts +6 -2
  29. package/dist/package-loader/resolve-consumed-contracts.js +30 -2
  30. package/dist/package-loader/resolve-consumed-contracts.test.js +43 -0
  31. package/package.json +1 -1
@@ -1,8 +1,8 @@
1
1
  export { ContractBuilder, definePackage, PackageBuilder } from "./builder";
2
2
  export { type ContractCallAuthorizer, type ContractProviderEndpointResolver, type ContractProviderPackageResolver, createContractProviderRouter, type IAuthorizedContractRoute, type IContractProviderEndpointRequest, type IContractProviderRouter, type IContractProviderRouterOptions, } from "./contract-provider-router";
3
3
  export { ContractProviders, ContractProvidersTable, contractProvidersMetaData, contractProvidersSchema, type IContractProviderRecord, } from "./contract-providers.table";
4
- export { CONTRACT_CALL_RPC, type ContractMemberType, type ContractNotificationSink, type ContractPermissionCheck, createContractConsumer, createContractProvider, createContractProviderEndpoint, createContractProviderSession, type IContractCall, type IContractCallContext, type IContractConsumer, type IContractConsumerBase, type IContractNotification, type IContractProviderEndpoint, type IContractProviderEndpointOptions, type IContractProviderSession, type IContractProviderSessionOptions, type IContractResolution, type IContractTableProxy, type IContractToolContext, type ITableProxy, sameUserContractPermissionCheck, } from "./contract-proxy";
5
- export { type ContractRemoteIdentity, type ContractResolver, createLocalUserContextContractProviderRouter, createUserContextContractProviderRouter, getContractResolver, handleContractCall, type IContractResolverRegistrationOptions, type ILocalContractVariableRequest, isSelfTrustedContractCaller, type LocalContractVariableResolver, registerContractResolver, } from "./contract-resolvers";
4
+ export { CONTRACT_CALL_RPC, type ContractMemberType, type ContractNotificationSink, type ContractPermissionCheck, createContractConsumer, createContractProvider, createContractProviderEndpoint, createContractProviderSession, createRemoteContractConsumer, type IContractCall, type IContractCallContext, type IContractConsumer, type IContractConsumerBase, type IContractNotification, type IContractProviderEndpoint, type IContractProviderEndpointOptions, type IContractProviderSession, type IContractProviderSessionOptions, type IContractResolution, type IContractTableProxy, type IContractToolContext, type ITableProxy, sameUserContractPermissionCheck, } from "./contract-proxy";
5
+ export { authorizeRemoteContractCall, type ContractRemoteIdentity, type ContractResolver, createLocalUserContextContractProviderRouter, createUserContextContractProviderRouter, getContractResolver, handleContractCall, type IContractResolverRegistrationOptions, type ILocalContractVariableRequest, isSelfTrustedContractCaller, type LocalContractVariableResolver, registerContractResolver, verifyRemoteContractIdentity, } from "./contract-resolvers";
6
6
  export { CONTRACT_NOTIFY_CHANNEL, CONTRACT_REQUEST_CHANNEL, connectionContractTransport, type IContractTransport, type IDuplexEmitter, inProcessTransportPair, } from "./contract-transport";
7
7
  export { Contracts, ContractsTable, contractsMetaData, contractsSchema, type IContractRecord, } from "./contracts.table";
8
8
  export { createLocalContractConsumer } from "./create-local-consumer";
@@ -10,11 +10,12 @@ export { extractTableShape, extractToolShape, type IExtractableTableDef, type IE
10
10
  export { getHostContractTransport, type HostContractTransportListener, registerHostContractTransport, subscribeHostContractTransport, waitForHostContractTransport, } from "./host-contract-transport";
11
11
  export { PersistentContractRegistry } from "./persistent-registry";
12
12
  export { ContractRegistry, type IContractProviderRegistration } from "./registry";
13
+ export { createRemoteContractEndpoint, getRemoteContractTransportFactory, type IRemoteContractHost, type IRemoteContractTarget, type RemoteContractTransportFactory, resolveRemoteTargetEndpoint, setRemoteContractTransportFactory, } from "./remote-contract-transport";
13
14
  export { DEVICE_OPERATIONS_CONTRACT_VERSION, DEVICE_OPERATIONS_MAX_QUERY_LENGTH, DEVICE_OPERATIONS_MAX_RESULT_BYTES, DEVICE_OPERATIONS_MAX_ROWS, DEVICE_OPERATIONS_MAX_TOOLS, type DeviceOperationCapability, deviceOperationsProviderPackageId, deviceOperationsToolIds, getDeviceOperationsContractDefinition, type IDeviceDatabaseQueryInput, type IDeviceDatabaseQueryResult, type IDeviceOperationStatus, type IDeviceToolListInput, type IDeviceToolLookupInput, type IDeviceToolLookupResult, type IDeviceToolRunInput, } from "./system/device-operations-contract";
14
15
  export { getLogsContractDefinition, type ILogsConsumer, LOGS_CONTRACT_VERSION, resolveLogsContract, } from "./system/logs-contract";
15
16
  export * from "./system/network-contracts";
16
17
  export { getToolAccessControlContractDefinition, type IToolAccessControlConsumer, TOOL_ACCESS_CONTROL_CONTRACT_VERSION, } from "./system/tool-access-control-contract";
17
18
  export { type ISystemContractRegistration, installSystemContracts, listSystemContracts, registerSystemContract, } from "./system-contracts";
18
- export type { IAlsoImplementsDeclaration, IConsumedContract, IConsumedContractHandle, IConsumedContractOptions, IContractDefinition, IContractEvent, IContractObservable, IContractTable, IContractTool, IPackageDefinitionResult, IResolvedContract, IValidationError, IValidationResult, } from "./types";
19
+ export type { IAlsoImplementsDeclaration, IConsumedContract, IConsumedContractHandle, IConsumedContractOptions, IContractDefinition, IContractEvent, IContractObservable, IContractTable, IContractTool, IPackageDefinitionResult, IRemoteContract, IRemoteContractConsumer, IRemoteContractHandle, IResolvedContract, IValidationError, IValidationResult, } from "./types";
19
20
  export { contractKey } from "./types";
20
21
  export { validateAlsoImplements, validateImmutability, validateProviderSatisfiesContract, } from "./validate";
@@ -14,8 +14,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.getToolAccessControlContractDefinition = exports.resolveLogsContract = exports.LOGS_CONTRACT_VERSION = exports.getLogsContractDefinition = exports.getDeviceOperationsContractDefinition = exports.deviceOperationsToolIds = exports.deviceOperationsProviderPackageId = exports.DEVICE_OPERATIONS_MAX_TOOLS = exports.DEVICE_OPERATIONS_MAX_ROWS = exports.DEVICE_OPERATIONS_MAX_RESULT_BYTES = exports.DEVICE_OPERATIONS_MAX_QUERY_LENGTH = exports.DEVICE_OPERATIONS_CONTRACT_VERSION = exports.ContractRegistry = exports.PersistentContractRegistry = exports.waitForHostContractTransport = exports.subscribeHostContractTransport = exports.registerHostContractTransport = exports.getHostContractTransport = exports.setSchemaToFieldsFn = exports.extractToolShape = exports.extractTableShape = exports.createLocalContractConsumer = exports.contractsSchema = exports.contractsMetaData = exports.ContractsTable = exports.Contracts = exports.inProcessTransportPair = exports.connectionContractTransport = exports.CONTRACT_REQUEST_CHANNEL = exports.CONTRACT_NOTIFY_CHANNEL = exports.registerContractResolver = exports.isSelfTrustedContractCaller = exports.handleContractCall = exports.getContractResolver = exports.createUserContextContractProviderRouter = exports.createLocalUserContextContractProviderRouter = exports.sameUserContractPermissionCheck = exports.createContractProviderSession = exports.createContractProviderEndpoint = exports.createContractProvider = exports.createContractConsumer = exports.CONTRACT_CALL_RPC = exports.contractProvidersSchema = exports.contractProvidersMetaData = exports.ContractProvidersTable = exports.ContractProviders = exports.createContractProviderRouter = exports.PackageBuilder = exports.definePackage = exports.ContractBuilder = void 0;
18
- exports.validateProviderSatisfiesContract = exports.validateImmutability = exports.validateAlsoImplements = exports.contractKey = exports.registerSystemContract = exports.listSystemContracts = exports.installSystemContracts = exports.TOOL_ACCESS_CONTROL_CONTRACT_VERSION = void 0;
17
+ exports.DEVICE_OPERATIONS_MAX_TOOLS = exports.DEVICE_OPERATIONS_MAX_ROWS = exports.DEVICE_OPERATIONS_MAX_RESULT_BYTES = exports.DEVICE_OPERATIONS_MAX_QUERY_LENGTH = exports.DEVICE_OPERATIONS_CONTRACT_VERSION = exports.setRemoteContractTransportFactory = exports.resolveRemoteTargetEndpoint = exports.getRemoteContractTransportFactory = exports.createRemoteContractEndpoint = exports.ContractRegistry = exports.PersistentContractRegistry = exports.waitForHostContractTransport = exports.subscribeHostContractTransport = exports.registerHostContractTransport = exports.getHostContractTransport = exports.setSchemaToFieldsFn = exports.extractToolShape = exports.extractTableShape = exports.createLocalContractConsumer = exports.contractsSchema = exports.contractsMetaData = exports.ContractsTable = exports.Contracts = exports.inProcessTransportPair = exports.connectionContractTransport = exports.CONTRACT_REQUEST_CHANNEL = exports.CONTRACT_NOTIFY_CHANNEL = exports.verifyRemoteContractIdentity = exports.registerContractResolver = exports.isSelfTrustedContractCaller = exports.handleContractCall = exports.getContractResolver = exports.createUserContextContractProviderRouter = exports.createLocalUserContextContractProviderRouter = exports.authorizeRemoteContractCall = exports.sameUserContractPermissionCheck = exports.createRemoteContractConsumer = exports.createContractProviderSession = exports.createContractProviderEndpoint = exports.createContractProvider = exports.createContractConsumer = exports.CONTRACT_CALL_RPC = exports.contractProvidersSchema = exports.contractProvidersMetaData = exports.ContractProvidersTable = exports.ContractProviders = exports.createContractProviderRouter = exports.PackageBuilder = exports.definePackage = exports.ContractBuilder = void 0;
18
+ exports.validateProviderSatisfiesContract = exports.validateImmutability = exports.validateAlsoImplements = exports.contractKey = exports.registerSystemContract = exports.listSystemContracts = exports.installSystemContracts = exports.TOOL_ACCESS_CONTROL_CONTRACT_VERSION = exports.getToolAccessControlContractDefinition = exports.resolveLogsContract = exports.LOGS_CONTRACT_VERSION = exports.getLogsContractDefinition = exports.getDeviceOperationsContractDefinition = exports.deviceOperationsToolIds = exports.deviceOperationsProviderPackageId = void 0;
19
19
  var builder_1 = require("./builder");
20
20
  Object.defineProperty(exports, "ContractBuilder", { enumerable: true, get: function () { return builder_1.ContractBuilder; } });
21
21
  Object.defineProperty(exports, "definePackage", { enumerable: true, get: function () { return builder_1.definePackage; } });
@@ -33,14 +33,17 @@ Object.defineProperty(exports, "createContractConsumer", { enumerable: true, get
33
33
  Object.defineProperty(exports, "createContractProvider", { enumerable: true, get: function () { return contract_proxy_1.createContractProvider; } });
34
34
  Object.defineProperty(exports, "createContractProviderEndpoint", { enumerable: true, get: function () { return contract_proxy_1.createContractProviderEndpoint; } });
35
35
  Object.defineProperty(exports, "createContractProviderSession", { enumerable: true, get: function () { return contract_proxy_1.createContractProviderSession; } });
36
+ Object.defineProperty(exports, "createRemoteContractConsumer", { enumerable: true, get: function () { return contract_proxy_1.createRemoteContractConsumer; } });
36
37
  Object.defineProperty(exports, "sameUserContractPermissionCheck", { enumerable: true, get: function () { return contract_proxy_1.sameUserContractPermissionCheck; } });
37
38
  var contract_resolvers_1 = require("./contract-resolvers");
39
+ Object.defineProperty(exports, "authorizeRemoteContractCall", { enumerable: true, get: function () { return contract_resolvers_1.authorizeRemoteContractCall; } });
38
40
  Object.defineProperty(exports, "createLocalUserContextContractProviderRouter", { enumerable: true, get: function () { return contract_resolvers_1.createLocalUserContextContractProviderRouter; } });
39
41
  Object.defineProperty(exports, "createUserContextContractProviderRouter", { enumerable: true, get: function () { return contract_resolvers_1.createUserContextContractProviderRouter; } });
40
42
  Object.defineProperty(exports, "getContractResolver", { enumerable: true, get: function () { return contract_resolvers_1.getContractResolver; } });
41
43
  Object.defineProperty(exports, "handleContractCall", { enumerable: true, get: function () { return contract_resolvers_1.handleContractCall; } });
42
44
  Object.defineProperty(exports, "isSelfTrustedContractCaller", { enumerable: true, get: function () { return contract_resolvers_1.isSelfTrustedContractCaller; } });
43
45
  Object.defineProperty(exports, "registerContractResolver", { enumerable: true, get: function () { return contract_resolvers_1.registerContractResolver; } });
46
+ Object.defineProperty(exports, "verifyRemoteContractIdentity", { enumerable: true, get: function () { return contract_resolvers_1.verifyRemoteContractIdentity; } });
44
47
  var contract_transport_1 = require("./contract-transport");
45
48
  Object.defineProperty(exports, "CONTRACT_NOTIFY_CHANNEL", { enumerable: true, get: function () { return contract_transport_1.CONTRACT_NOTIFY_CHANNEL; } });
46
49
  Object.defineProperty(exports, "CONTRACT_REQUEST_CHANNEL", { enumerable: true, get: function () { return contract_transport_1.CONTRACT_REQUEST_CHANNEL; } });
@@ -66,6 +69,11 @@ var persistent_registry_1 = require("./persistent-registry");
66
69
  Object.defineProperty(exports, "PersistentContractRegistry", { enumerable: true, get: function () { return persistent_registry_1.PersistentContractRegistry; } });
67
70
  var registry_1 = require("./registry");
68
71
  Object.defineProperty(exports, "ContractRegistry", { enumerable: true, get: function () { return registry_1.ContractRegistry; } });
72
+ var remote_contract_transport_1 = require("./remote-contract-transport");
73
+ Object.defineProperty(exports, "createRemoteContractEndpoint", { enumerable: true, get: function () { return remote_contract_transport_1.createRemoteContractEndpoint; } });
74
+ Object.defineProperty(exports, "getRemoteContractTransportFactory", { enumerable: true, get: function () { return remote_contract_transport_1.getRemoteContractTransportFactory; } });
75
+ Object.defineProperty(exports, "resolveRemoteTargetEndpoint", { enumerable: true, get: function () { return remote_contract_transport_1.resolveRemoteTargetEndpoint; } });
76
+ Object.defineProperty(exports, "setRemoteContractTransportFactory", { enumerable: true, get: function () { return remote_contract_transport_1.setRemoteContractTransportFactory; } });
69
77
  var device_operations_contract_1 = require("./system/device-operations-contract");
70
78
  Object.defineProperty(exports, "DEVICE_OPERATIONS_CONTRACT_VERSION", { enumerable: true, get: function () { return device_operations_contract_1.DEVICE_OPERATIONS_CONTRACT_VERSION; } });
71
79
  Object.defineProperty(exports, "DEVICE_OPERATIONS_MAX_QUERY_LENGTH", { enumerable: true, get: function () { return device_operations_contract_1.DEVICE_OPERATIONS_MAX_QUERY_LENGTH; } });
@@ -0,0 +1,69 @@
1
+ import type { UserContext } from "../context/user-context";
2
+ import type { IContractProviderEndpointRequest } from "./contract-provider-router";
3
+ import type { IContractProviderEndpoint } from "./contract-proxy";
4
+ import { type IContractTransport } from "./contract-transport";
5
+ /** Destination of a device-targeted contract call. */
6
+ export interface IRemoteContractTarget {
7
+ /** Device that should execute the call. */
8
+ deviceId: string;
9
+ /**
10
+ * Data context the call is scoped to. Required so the transport can seal the message
11
+ * to the right group or personal keys and the remote device can authorize membership.
12
+ */
13
+ dataContextId: string;
14
+ }
15
+ /**
16
+ * Host-registered factory that produces an outbound {@link IContractTransport} to one
17
+ * remote device. `peers-device` registers a `sendDeviceMessage`-backed implementation;
18
+ * the SDK stays transport-agnostic and only knows how to ask for one.
19
+ */
20
+ export type RemoteContractTransportFactory = (target: IRemoteContractTarget) => IContractTransport;
21
+ /** Trusted host state used to decide whether and how to forward a route. */
22
+ export interface IRemoteContractHost {
23
+ /** This device's id (`userContext.deviceId()`); a matching target resolves locally. */
24
+ localDeviceId?: string;
25
+ /** Owning user context, used to pick a per-context transport factory. */
26
+ userContext?: UserContext;
27
+ }
28
+ /**
29
+ * Register (or clear, with `undefined`) the factory used to reach other devices for
30
+ * contract tool calls. A connection manager registers one for its own `UserContext`;
31
+ * omitting `opts.userContext` sets the process-wide fallback. Call before any package
32
+ * invokes a `pkg.remote(...)` device consumer.
33
+ *
34
+ * @param factory Factory returning an outbound transport, or `undefined` to unregister.
35
+ * @param opts.userContext Scope the registration to one user context.
36
+ */
37
+ export declare function setRemoteContractTransportFactory(factory: RemoteContractTransportFactory | undefined, opts?: {
38
+ userContext?: UserContext;
39
+ }): void;
40
+ /**
41
+ * Return the transport factory for a user context, falling back to the process default.
42
+ *
43
+ * @param userContext Optional owning user context.
44
+ */
45
+ export declare function getRemoteContractTransportFactory(userContext?: UserContext): RemoteContractTransportFactory | undefined;
46
+ /**
47
+ * Shared chokepoint for trusted local hosts: when an authorized route names a device
48
+ * other than this one, return a forwarding endpoint; otherwise return `undefined` so the
49
+ * caller resolves the provider locally. A target equal to the local device is treated as
50
+ * local so `remote.device(myOwnDeviceId)` behaves like a plain consumer.
51
+ *
52
+ * @param request Authorized route from a router serving a *local* principal.
53
+ * @param host This device's id and owning user context.
54
+ */
55
+ export declare function resolveRemoteTargetEndpoint(request: IContractProviderEndpointRequest, host: IRemoteContractHost): IContractProviderEndpoint | undefined;
56
+ /**
57
+ * Build a provider endpoint that forwards authorized calls to another device.
58
+ *
59
+ * The endpoint is created only by a trusted local host after it has authorized the
60
+ * *local* caller for the route. It strips {@link IContractCall.targetDeviceId} before
61
+ * emitting so the receiving device sees an ordinary call addressed to itself and the
62
+ * message is signed with this device's own identity. Only tool runs are forwarded; the
63
+ * router rejects other members before reaching this endpoint.
64
+ *
65
+ * @param request Authorized route whose `targetDeviceId` and `dataContextId` are set.
66
+ * @param factory Transport factory; defaults to the process-wide registration.
67
+ * @throws If no factory is available or the route lacks a target device or data context.
68
+ */
69
+ export declare function createRemoteContractEndpoint(request: IContractProviderEndpointRequest, factory?: RemoteContractTransportFactory | undefined): IContractProviderEndpoint;
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setRemoteContractTransportFactory = setRemoteContractTransportFactory;
4
+ exports.getRemoteContractTransportFactory = getRemoteContractTransportFactory;
5
+ exports.resolveRemoteTargetEndpoint = resolveRemoteTargetEndpoint;
6
+ exports.createRemoteContractEndpoint = createRemoteContractEndpoint;
7
+ const contract_transport_1 = require("./contract-transport");
8
+ const factoriesByUserContext = new WeakMap();
9
+ let defaultFactory;
10
+ /**
11
+ * Register (or clear, with `undefined`) the factory used to reach other devices for
12
+ * contract tool calls. A connection manager registers one for its own `UserContext`;
13
+ * omitting `opts.userContext` sets the process-wide fallback. Call before any package
14
+ * invokes a `pkg.remote(...)` device consumer.
15
+ *
16
+ * @param factory Factory returning an outbound transport, or `undefined` to unregister.
17
+ * @param opts.userContext Scope the registration to one user context.
18
+ */
19
+ function setRemoteContractTransportFactory(factory, opts = {}) {
20
+ if (opts.userContext) {
21
+ if (factory) {
22
+ factoriesByUserContext.set(opts.userContext, factory);
23
+ }
24
+ else {
25
+ factoriesByUserContext.delete(opts.userContext);
26
+ }
27
+ return;
28
+ }
29
+ defaultFactory = factory;
30
+ }
31
+ /**
32
+ * Return the transport factory for a user context, falling back to the process default.
33
+ *
34
+ * @param userContext Optional owning user context.
35
+ */
36
+ function getRemoteContractTransportFactory(userContext) {
37
+ return (userContext && factoriesByUserContext.get(userContext)) ?? defaultFactory;
38
+ }
39
+ /**
40
+ * Shared chokepoint for trusted local hosts: when an authorized route names a device
41
+ * other than this one, return a forwarding endpoint; otherwise return `undefined` so the
42
+ * caller resolves the provider locally. A target equal to the local device is treated as
43
+ * local so `remote.device(myOwnDeviceId)` behaves like a plain consumer.
44
+ *
45
+ * @param request Authorized route from a router serving a *local* principal.
46
+ * @param host This device's id and owning user context.
47
+ */
48
+ function resolveRemoteTargetEndpoint(request, host) {
49
+ if (!request.targetDeviceId || request.targetDeviceId === host.localDeviceId) {
50
+ return undefined;
51
+ }
52
+ return createRemoteContractEndpoint(request, getRemoteContractTransportFactory(host.userContext));
53
+ }
54
+ /**
55
+ * Build a provider endpoint that forwards authorized calls to another device.
56
+ *
57
+ * The endpoint is created only by a trusted local host after it has authorized the
58
+ * *local* caller for the route. It strips {@link IContractCall.targetDeviceId} before
59
+ * emitting so the receiving device sees an ordinary call addressed to itself and the
60
+ * message is signed with this device's own identity. Only tool runs are forwarded; the
61
+ * router rejects other members before reaching this endpoint.
62
+ *
63
+ * @param request Authorized route whose `targetDeviceId` and `dataContextId` are set.
64
+ * @param factory Transport factory; defaults to the process-wide registration.
65
+ * @throws If no factory is available or the route lacks a target device or data context.
66
+ */
67
+ function createRemoteContractEndpoint(request, factory = getRemoteContractTransportFactory()) {
68
+ const { targetDeviceId, dataContextId } = request;
69
+ if (!targetDeviceId) {
70
+ throw new Error("Remote contract endpoint requires a target device id");
71
+ }
72
+ if (!dataContextId) {
73
+ throw new Error("Remote contract endpoint requires an authorized data context id");
74
+ }
75
+ if (!factory) {
76
+ throw new Error(`Cannot reach device ${targetDeviceId}: no remote contract transport is registered on this host`);
77
+ }
78
+ const transport = factory({ deviceId: targetDeviceId, dataContextId });
79
+ let disposed = false;
80
+ return {
81
+ handleCall(call) {
82
+ if (disposed) {
83
+ return Promise.reject(new Error(`Remote contract endpoint for ${targetDeviceId} is disposed`));
84
+ }
85
+ if (call.member !== "tool" || call.operation !== "run") {
86
+ return Promise.reject(new Error(`Contract ${call.member} '${call.name}'.${call.operation} cannot be forwarded to a remote device`));
87
+ }
88
+ const { targetDeviceId: _stripped, ...outbound } = call;
89
+ return transport.emit(contract_transport_1.CONTRACT_REQUEST_CHANNEL, { ...outbound, dataContextId });
90
+ },
91
+ dispose() {
92
+ disposed = true;
93
+ },
94
+ };
95
+ }
@@ -39,6 +39,13 @@ export interface IContractTool {
39
39
  usageDescription: string;
40
40
  inputFields: IField[];
41
41
  outputFields: IField[];
42
+ /**
43
+ * Minimum group access level (0–100) a caller on **another device** needs to run
44
+ * this tool. Absent means the tool is local-only: consumers on the same device can
45
+ * call it, but remote devices are denied. Self-trusted callers (the provider's own
46
+ * account) are never gated by this value. Policy only — not part of shape validation.
47
+ */
48
+ remoteAccessLevel?: number;
42
49
  }
43
50
  /** Observable value exposed by a contract. */
44
51
  export interface IContractObservable {
@@ -111,6 +118,51 @@ export interface IConsumedContract {
111
118
  /** Deferred handle returned by `consumes()` for this dependency. */
112
119
  handle?: IConsumedContractHandle<IContractConsumerBase | undefined>;
113
120
  }
121
+ /**
122
+ * Resolved value of a {@link PackageBuilder.remote} handle: a factory of tools-only
123
+ * consumers whose calls execute on other devices.
124
+ *
125
+ * Unlike a consumed contract, no local provider is required — the executing device
126
+ * selects its own provider and authorizes the call against the caller's group role and
127
+ * the tool's {@link IContractTool.remoteAccessLevel}. Only `tools` are reachable on the
128
+ * returned consumers today; `tables`, `observables`, and `events` throw on access.
129
+ *
130
+ * @typeParam T - Consumer proxy type (defaults to {@link IContractConsumer}).
131
+ */
132
+ export interface IRemoteContractConsumer<T extends IContractConsumerBase = IContractConsumer> {
133
+ contractId: string;
134
+ version: number;
135
+ /**
136
+ * Consumer whose tool calls run on `deviceId`. Instances are cached per device; passing
137
+ * this device's own id behaves like a plain local consumer.
138
+ *
139
+ * @param deviceId Device that should run the calls.
140
+ */
141
+ device(deviceId: string): T;
142
+ /** Idempotently dispose every per-device consumer created by this factory. */
143
+ dispose(): void;
144
+ }
145
+ /**
146
+ * Deferred handle returned by {@link PackageBuilder.remote}.
147
+ *
148
+ * Like {@link IConsumedContractHandle}, the handle is not thenable — await
149
+ * {@link consumer} from runtime code after `definePackage` finishes. The
150
+ * package-loader settles it once the contract definition is known locally.
151
+ */
152
+ export interface IRemoteContractHandle<T extends IContractConsumerBase = IContractConsumer> {
153
+ /** Resolves to the per-device consumer factory. */
154
+ readonly consumer: Promise<IRemoteContractConsumer<T>>;
155
+ }
156
+ /**
157
+ * A contract a package calls on other devices. Recorded during `definePackage`;
158
+ * the loader settles the handle without requiring a local provider.
159
+ */
160
+ export interface IRemoteContract {
161
+ contractId: string;
162
+ version: number;
163
+ /** Deferred handle returned by `remote()` for this declaration. */
164
+ handle?: IRemoteContractHandle<IContractConsumerBase>;
165
+ }
114
166
  /**
115
167
  * Declares that the owning contract's implementation also satisfies another
116
168
  * contract (single version or inclusive range).
@@ -132,6 +184,11 @@ export interface IPackageDefinitionResult {
132
184
  contracts: IContractDefinition[];
133
185
  /** Contracts this package depends on. */
134
186
  consumes: IConsumedContract[];
187
+ /**
188
+ * Contracts this package calls on other devices via `pkg.remote()`. Optional so
189
+ * bundles built with an older SDK still load.
190
+ */
191
+ remotes?: IRemoteContract[];
135
192
  /** `alsoImplements` declarations keyed by the owning contractId+version. */
136
193
  alsoImplements: Map<string, IAlsoImplementsDeclaration[]>;
137
194
  /** Package-level items not part of any contract. */
@@ -220,6 +220,7 @@ export declare const toolSchema: z.ZodObject<{
220
220
  isAssistantRunner: z.ZodOptional<z.ZodBoolean>;
221
221
  packageId: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
222
222
  accessLevel: z.ZodDefault<z.ZodNumber>;
223
+ remoteAccessLevel: z.ZodOptional<z.ZodNumber>;
223
224
  }, "strip", z.ZodTypeAny, {
224
225
  code: string;
225
226
  name: string;
@@ -266,6 +267,7 @@ export declare const toolSchema: z.ZodObject<{
266
267
  codeRepository?: string | undefined;
267
268
  isAssistantRunner?: boolean | undefined;
268
269
  packageId?: string | undefined;
270
+ remoteAccessLevel?: number | undefined;
269
271
  }, {
270
272
  name: string;
271
273
  toolId: string;
@@ -312,6 +314,7 @@ export declare const toolSchema: z.ZodObject<{
312
314
  isAssistantRunner?: boolean | undefined;
313
315
  packageId?: string | undefined;
314
316
  accessLevel?: number | undefined;
317
+ remoteAccessLevel?: number | undefined;
315
318
  }>;
316
319
  /** A tool definition stored in the Tools table. */
317
320
  export type ITool = z.infer<typeof toolSchema>;
@@ -373,4 +376,5 @@ export declare function Tools(dataContext?: DataContext): import("..").Table<{
373
376
  codeRepository?: string | undefined;
374
377
  isAssistantRunner?: boolean | undefined;
375
378
  packageId?: string | undefined;
379
+ remoteAccessLevel?: number | undefined;
376
380
  }>;
@@ -49,6 +49,13 @@ exports.toolSchema = zod_1.z.object({
49
49
  .describe("Whether this tool is a model that can be used to run an assistant"),
50
50
  packageId: zod_types_1.zodPeerId.optional().describe("The package that installed this tool, if any"),
51
51
  accessLevel: access_level_1.accessLevelSchema.describe("Required access level (0-100) to run this tool. Unclassified tools default to 100 (Self)."),
52
+ remoteAccessLevel: zod_1.z
53
+ .number()
54
+ .int()
55
+ .min(access_level_1.MIN_ACCESS_LEVEL)
56
+ .max(access_level_1.MAX_ACCESS_LEVEL)
57
+ .optional()
58
+ .describe("Minimum group access level (0-100) a caller on another device needs to run this tool. Omit to keep the tool local-only."),
52
59
  });
53
60
  const metaData = {
54
61
  name: "Tools",
@@ -228,7 +228,14 @@ function validateProvidedContract(contract, index, packageTables) {
228
228
  if (!tool || typeof tool !== "object" || Array.isArray(tool)) {
229
229
  throw new Error(`${toolLabel} must be an object`);
230
230
  }
231
- assertOnlyKeys(tool, ["name", "usageDescription", "inputFields", "outputFields", "suggestedAccessLevel"], toolLabel);
231
+ assertOnlyKeys(tool, [
232
+ "name",
233
+ "usageDescription",
234
+ "inputFields",
235
+ "outputFields",
236
+ "suggestedAccessLevel",
237
+ "remoteAccessLevel",
238
+ ], toolLabel);
232
239
  if (typeof tool.name !== "string" || tool.name.trim() === "") {
233
240
  throw new Error(`${toolLabel}.name is required`);
234
241
  }
@@ -248,6 +255,13 @@ function validateProvidedContract(contract, index, packageTables) {
248
255
  throw new Error(`${toolLabel}.suggestedAccessLevel must be an integer 0–100`);
249
256
  }
250
257
  }
258
+ if (tool.remoteAccessLevel !== undefined) {
259
+ if (!Number.isInteger(tool.remoteAccessLevel) ||
260
+ tool.remoteAccessLevel < access_level_1.MIN_ACCESS_LEVEL ||
261
+ tool.remoteAccessLevel > access_level_1.MAX_ACCESS_LEVEL) {
262
+ throw new Error(`${toolLabel}.remoteAccessLevel must be an integer 0–100`);
263
+ }
264
+ }
251
265
  }
252
266
  if (contract.tables === undefined) {
253
267
  contract.tables = [];
@@ -530,6 +544,7 @@ function isolatedContractToDefinition(contract, packageTables) {
530
544
  usageDescription: tool.usageDescription,
531
545
  inputFields: tool.inputFields ?? [],
532
546
  outputFields: tool.outputFields ?? [],
547
+ ...(tool.remoteAccessLevel !== undefined ? { remoteAccessLevel: tool.remoteAccessLevel } : {}),
533
548
  }));
534
549
  const tables = (contract.tables ?? []).map((tableRef) => {
535
550
  const table = findPackageTable(packageTables, tableRef.name);
@@ -168,8 +168,11 @@ export interface IIsolatedPackagePersistentVarGateway {
168
168
  export interface IIsolatedContractProviderEndpointRequest extends IContractProviderEndpointRequest {
169
169
  /** Installed isolated package making the consumed-contract call. */
170
170
  consumerPackageId: string;
171
- /** Registry-selected provider package. */
172
- providerPackageId: string;
171
+ /**
172
+ * Registry-selected provider package. Omitted for a device-targeted route
173
+ * (`targetDeviceId` set), where the executing device selects the provider.
174
+ */
175
+ providerPackageId?: string;
173
176
  /** Optional deadline the gateway should enforce for provider setup/calls. */
174
177
  timeoutMs?: number;
175
178
  }
@@ -2,7 +2,7 @@ import type { DataContext } from "../context/data-context";
2
2
  import { type IContractProviderEndpointRequest } from "../contracts/contract-provider-router";
3
3
  import { type IContractConsumer, type IContractProviderEndpoint } from "../contracts/contract-proxy";
4
4
  import { ContractRegistry } from "../contracts/registry";
5
- import type { IConsumedContract } from "../contracts/types";
5
+ import type { IConsumedContract, IRemoteContract, IRemoteContractConsumer } from "../contracts/types";
6
6
  import { type IPackage } from "../data/packages";
7
7
  import type { IPeersPackage } from "../types/peers-package";
8
8
  import { type IIsolatedPackageRuntime } from "./isolated-runtime";
@@ -96,6 +96,20 @@ export declare class PackageLoader {
96
96
  * @returns A live consumer proxy, or `undefined` when no provider matches.
97
97
  */
98
98
  resolveConsumedContract(dependency: IConsumedContract): Promise<IContractConsumer | undefined>;
99
+ /**
100
+ * Bind a legacy package's `remote()` declaration to a per-device consumer factory.
101
+ *
102
+ * No local provider is selected: every device consumer stamps `targetDeviceId` and
103
+ * this loader's own router forwards the call through
104
+ * {@link resolveContractEndpoint}, which hands remote targets to the mesh transport
105
+ * and resolves this device's id locally. The contract definition must already be
106
+ * registered in this context (from the package's own `provides` or another
107
+ * installed package) so the proxy knows the tool names.
108
+ *
109
+ * @param declaration - Validated package remote declaration.
110
+ * @returns A per-device consumer factory, or `undefined` when the contract is unknown.
111
+ */
112
+ resolveRemoteContract(declaration: IRemoteContract): Promise<IRemoteContractConsumer<IContractConsumer> | undefined>;
99
113
  /**
100
114
  * Resolve a sandbox-backed provider endpoint when this context owns an
101
115
  * isolated provider for the requested contract.
@@ -7,6 +7,7 @@ const contract_proxy_1 = require("../contracts/contract-proxy");
7
7
  const contract_resolvers_1 = require("../contracts/contract-resolvers");
8
8
  const contract_transport_1 = require("../contracts/contract-transport");
9
9
  const registry_1 = require("../contracts/registry");
10
+ const remote_contract_transport_1 = require("../contracts/remote-contract-transport");
10
11
  const system_contracts_1 = require("../contracts/system-contracts");
11
12
  const files_1 = require("../data/files");
12
13
  const package_version_resolver_1 = require("../data/package-version-resolver");
@@ -132,6 +133,16 @@ class PackageLoader {
132
133
  * @throws If no provider or matching endpoint implementation is available.
133
134
  */
134
135
  async resolveContractEndpoint(request, opts = {}) {
136
+ // A route that names another device is forwarded over the mesh instead of being
137
+ // resolved against a local provider. Only routers serving local principals set
138
+ // targetDeviceId; remote-caller routers reject it before reaching this point.
139
+ const remoteEndpoint = (0, remote_contract_transport_1.resolveRemoteTargetEndpoint)(request, {
140
+ localDeviceId: this.dataContext.userContext?.deviceId(),
141
+ userContext: this.dataContext.userContext,
142
+ });
143
+ if (remoteEndpoint) {
144
+ return remoteEndpoint;
145
+ }
135
146
  const providerPackageId = opts.providerPackageId ??
136
147
  request.providerPackageId ??
137
148
  this.contractRegistry.getProviderPackageId(request.contractId, request.version);
@@ -244,6 +255,43 @@ class PackageLoader {
244
255
  };
245
256
  return consumer;
246
257
  }
258
+ /**
259
+ * Bind a legacy package's `remote()` declaration to a per-device consumer factory.
260
+ *
261
+ * No local provider is selected: every device consumer stamps `targetDeviceId` and
262
+ * this loader's own router forwards the call through
263
+ * {@link resolveContractEndpoint}, which hands remote targets to the mesh transport
264
+ * and resolves this device's id locally. The contract definition must already be
265
+ * registered in this context (from the package's own `provides` or another
266
+ * installed package) so the proxy knows the tool names.
267
+ *
268
+ * @param declaration - Validated package remote declaration.
269
+ * @returns A per-device consumer factory, or `undefined` when the contract is unknown.
270
+ */
271
+ async resolveRemoteContract(declaration) {
272
+ const definition = this.contractRegistry.getDefinition(declaration.contractId, declaration.version);
273
+ if (!definition) {
274
+ return undefined;
275
+ }
276
+ const [consumerTransport, hostTransport] = (0, contract_transport_1.inProcessTransportPair)();
277
+ const router = (0, contract_provider_router_1.createContractProviderRouter)(hostTransport, {
278
+ authorizeCall: () => ({
279
+ dataContextId: this.dataContext.dataContextId,
280
+ context: { dataContext: this.dataContext },
281
+ }),
282
+ resolveProviderPackageId: (request) => this.contractRegistry.getProviderPackageId(request.contractId, request.version),
283
+ resolveEndpoint: (request) => this.resolveContractEndpoint(request),
284
+ });
285
+ const remote = (0, contract_proxy_1.createRemoteContractConsumer)(definition, consumerTransport, {
286
+ dataContextId: this.dataContext.dataContextId,
287
+ });
288
+ const dispose = remote.dispose.bind(remote);
289
+ remote.dispose = () => {
290
+ dispose();
291
+ router.dispose();
292
+ };
293
+ return remote;
294
+ }
247
295
  /**
248
296
  * Resolve a sandbox-backed provider endpoint when this context owns an
249
297
  * isolated provider for the requested contract.
@@ -2,12 +2,16 @@ import type { DataContext } from "../context/data-context";
2
2
  import type { ContractRegistry } from "../contracts/registry";
3
3
  import type { IPackageDefinitionResult } from "../contracts/types";
4
4
  /**
5
- * Settle each deferred `consumes` handle on a package definition after its
6
- * providers have been registered.
5
+ * Settle each deferred `consumes` and `remote` handle on a package definition
6
+ * after its providers have been registered.
7
7
  *
8
8
  * A consume succeeds only when {@link ContractRegistry.selectProvider} finds a
9
9
  * matching provider and the owning PackageLoader can bind an endpoint. The
10
10
  * endpoint may be resolver-backed or isolated. Missing optional deps settle to
11
11
  * `undefined`; missing required deps reject the handle.
12
+ *
13
+ * A remote declaration needs no local provider: it settles to a per-device
14
+ * consumer factory as long as the contract definition is registered locally and
15
+ * a PackageLoader owns the context; otherwise the handle rejects.
12
16
  */
13
17
  export declare function resolveConsumedContracts(dataContext: DataContext, registry: ContractRegistry, packageDefinition: IPackageDefinitionResult): Promise<void>;
@@ -5,15 +5,20 @@ const builder_1 = require("../contracts/builder");
5
5
  const contract_resolvers_1 = require("../contracts/contract-resolvers");
6
6
  const create_local_consumer_1 = require("../contracts/create-local-consumer");
7
7
  /**
8
- * Settle each deferred `consumes` handle on a package definition after its
9
- * providers have been registered.
8
+ * Settle each deferred `consumes` and `remote` handle on a package definition
9
+ * after its providers have been registered.
10
10
  *
11
11
  * A consume succeeds only when {@link ContractRegistry.selectProvider} finds a
12
12
  * matching provider and the owning PackageLoader can bind an endpoint. The
13
13
  * endpoint may be resolver-backed or isolated. Missing optional deps settle to
14
14
  * `undefined`; missing required deps reject the handle.
15
+ *
16
+ * A remote declaration needs no local provider: it settles to a per-device
17
+ * consumer factory as long as the contract definition is registered locally and
18
+ * a PackageLoader owns the context; otherwise the handle rejects.
15
19
  */
16
20
  async function resolveConsumedContracts(dataContext, registry, packageDefinition) {
21
+ await resolveRemoteContracts(dataContext, packageDefinition);
17
22
  for (const dep of packageDefinition.consumes) {
18
23
  const handle = dep.handle;
19
24
  if (!handle) {
@@ -44,6 +49,29 @@ async function resolveConsumedContracts(dataContext, registry, packageDefinition
44
49
  }
45
50
  }
46
51
  }
52
+ async function resolveRemoteContracts(dataContext, packageDefinition) {
53
+ for (const declaration of packageDefinition.remotes ?? []) {
54
+ const handle = declaration.handle;
55
+ if (!handle) {
56
+ continue;
57
+ }
58
+ const key = `${declaration.contractId}@${declaration.version}`;
59
+ try {
60
+ const loader = dataContext.packageLoader;
61
+ if (!loader?.resolveRemoteContract) {
62
+ throw new Error(`Remote contract ${key} requires a package loader on the data context`);
63
+ }
64
+ const remote = await loader.resolveRemoteContract(declaration);
65
+ if (!remote) {
66
+ throw new Error(`Remote contract ${key} could not be resolved: its definition is not registered on this device`);
67
+ }
68
+ (0, builder_1.settleContractHandle)(handle, remote);
69
+ }
70
+ catch (err) {
71
+ (0, builder_1.rejectContractHandle)(handle, err);
72
+ }
73
+ }
74
+ }
47
75
  async function resolveLegacyLocalConsumer(dataContext, selected, contractId, version) {
48
76
  const resolver = (0, contract_resolvers_1.getContractResolver)(contractId, version, selected.providerPackageId);
49
77
  if (!resolver) {
@@ -120,4 +120,47 @@ describe("resolveConsumedContracts", () => {
120
120
  }
121
121
  await expect(consumeHandle.consumer).resolves.toBeUndefined();
122
122
  });
123
+ describe("remote declarations", () => {
124
+ it("settles a remote handle through the package loader without a local provider", async () => {
125
+ const remote = { contractId, version: 1, device: jest.fn(), dispose: jest.fn() };
126
+ const resolveRemoteContract = jest.fn().mockResolvedValue(remote);
127
+ const dataContext = {
128
+ dataContextId: "test-ctx",
129
+ packageLoader: { resolveRemoteContract },
130
+ };
131
+ let remoteHandle;
132
+ const packageDefinition = (0, builder_1.definePackage)((pkg) => {
133
+ pkg.packageId = (0, utils_1.newid)();
134
+ pkg.version = "1.0.0";
135
+ remoteHandle = pkg.remote(contractId, 1);
136
+ });
137
+ await (0, resolve_consumed_contracts_1.resolveConsumedContracts)(dataContext, new registry_1.ContractRegistry(), packageDefinition);
138
+ expect(resolveRemoteContract).toHaveBeenCalledWith(expect.objectContaining({ contractId, version: 1 }));
139
+ await expect(remoteHandle?.consumer).resolves.toBe(remote);
140
+ });
141
+ it("rejects a remote handle when the contract definition is unknown locally", async () => {
142
+ const dataContext = {
143
+ dataContextId: "test-ctx",
144
+ packageLoader: { resolveRemoteContract: jest.fn().mockResolvedValue(undefined) },
145
+ };
146
+ let remoteHandle;
147
+ const packageDefinition = (0, builder_1.definePackage)((pkg) => {
148
+ pkg.packageId = (0, utils_1.newid)();
149
+ pkg.version = "1.0.0";
150
+ remoteHandle = pkg.remote(contractId, 1);
151
+ });
152
+ await (0, resolve_consumed_contracts_1.resolveConsumedContracts)(dataContext, new registry_1.ContractRegistry(), packageDefinition);
153
+ await expect(remoteHandle?.consumer).rejects.toThrow(/not registered on this device/);
154
+ });
155
+ it("rejects a remote handle when no package loader owns the context", async () => {
156
+ let remoteHandle;
157
+ const packageDefinition = (0, builder_1.definePackage)((pkg) => {
158
+ pkg.packageId = (0, utils_1.newid)();
159
+ pkg.version = "1.0.0";
160
+ remoteHandle = pkg.remote(contractId, 1);
161
+ });
162
+ await (0, resolve_consumed_contracts_1.resolveConsumedContracts)(mockDataContext(), new registry_1.ContractRegistry(), packageDefinition);
163
+ await expect(remoteHandle?.consumer).rejects.toThrow(/requires a package loader/);
164
+ });
165
+ });
123
166
  });