@agent-surface/orpc 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Wiseair S.r.l.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # @agent-surface/orpc
2
+
3
+ Contextual references to oRPC domain procedures for [agent-surface](https://github.com/Wiseair-srl/agent-surface). A frontend never redefines a domain operation: it **references** one exposed via [orpc-agent](https://orpc-agent.dev) — same identity, same server authority — and adds the three things only the frontend can know: whether it is relevant right now, which inputs come from UI state, and what the user must confirm before it runs.
4
+
5
+ Docs: https://agent-surface-docs.vercel.app
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pnpm add @agent-surface/core @agent-surface/orpc
11
+ ```
12
+
13
+ ## Use
14
+
15
+ ```ts
16
+ import { createOrpcAgentBridge } from "@agent-surface/orpc";
17
+
18
+ export const bridge = createOrpcAgentBridge({
19
+ client: orpcClient, // the app's existing typed oRPC client
20
+ manifest: agentManifest, // which procedures orpc-agent exposes (the ceiling)
21
+ });
22
+ registry.setProcedureExecutor(bridge.executor);
23
+ ```
24
+
25
+ ```tsx
26
+ import { useAgentProcedure } from "@agent-surface/orpc/react";
27
+
28
+ useAgentProcedure(bridge.refs.devices.disable, {
29
+ when: () => selectedIds.length > 0,
30
+ unavailableReason: "Select at least one device first",
31
+ bind: () => ({ deviceIds: selectedIds }), // locked: the agent cannot override it
32
+ confirmation: "required",
33
+ });
34
+ ```
35
+
36
+ Bound fields are removed from the agent-facing schema and locked by default; `bind()` runs at execution time on live UI state; the merged input is re-validated against the full schema before forwarding; the server re-validates everything regardless.
37
+
38
+ Full specification: [docs/05](https://github.com/Wiseair-srl/agent-surface/blob/main/docs/05-orpc-integration.md).
39
+
40
+ MIT © Wiseair S.r.l.
@@ -0,0 +1,211 @@
1
+ // src/bridge.ts
2
+ import {
3
+ AgentSurfaceError
4
+ } from "@agent-surface/core";
5
+ var BRIDGE_REF = /* @__PURE__ */ Symbol("agent-surface.orpc-ref");
6
+ function walkClient(client, path) {
7
+ let node = client;
8
+ for (const segment of path.split(".")) {
9
+ if (typeof node !== "object" || node === null) return void 0;
10
+ const next = node[segment];
11
+ if (next === void 0) return void 0;
12
+ node = next;
13
+ }
14
+ return typeof node === "function" ? node : void 0;
15
+ }
16
+ function isRecord(v) {
17
+ return typeof v === "object" && v !== null;
18
+ }
19
+ function defaultMapServerError(error) {
20
+ if (!isRecord(error)) return void 0;
21
+ const code = error.code ?? error.status;
22
+ if (code === "UNAUTHORIZED" || code === "FORBIDDEN" || code === 401 || code === 403) {
23
+ return {
24
+ code: "NOT_AUTHORIZED",
25
+ message: "The server rejected this call as not authorized.",
26
+ retry: "no",
27
+ details: { origin: "server" }
28
+ };
29
+ }
30
+ const data = isRecord(error.data) ? error.data : void 0;
31
+ if (code === "APPROVAL_REQUIRED" || data?.approvalRequired === true) {
32
+ return {
33
+ code: "CONFIRMATION_REQUIRED",
34
+ message: "The server requires its own approval for this operation. Wait for approval, then retry.",
35
+ retry: "with-confirmation",
36
+ details: {
37
+ origin: "server",
38
+ ...typeof data?.approvalId === "string" ? { confirmationId: data.approvalId } : {}
39
+ }
40
+ };
41
+ }
42
+ return void 0;
43
+ }
44
+ function createOrpcAgentBridge(options) {
45
+ const client = options.client;
46
+ const { manifest } = options;
47
+ const mapError = options.mapServerError ?? defaultMapServerError;
48
+ const refs = {};
49
+ for (const [path, tool] of Object.entries(manifest.tools)) {
50
+ const segments = path.split(".");
51
+ let node = refs;
52
+ for (const segment of segments.slice(0, -1)) {
53
+ node[segment] = node[segment] ?? {};
54
+ node = node[segment];
55
+ }
56
+ const leaf = segments[segments.length - 1];
57
+ const ref = {
58
+ id: `domain:${path}`,
59
+ path,
60
+ description: tool.description,
61
+ inputSchema: tool.inputSchema,
62
+ ...tool.outputSchema ? { outputSchema: tool.outputSchema } : {},
63
+ effect: tool.effect,
64
+ ...tool.requiresApproval !== void 0 ? { requiresApproval: tool.requiresApproval } : {},
65
+ [BRIDGE_REF]: true,
66
+ async call(input, ctx) {
67
+ const fn = walkClient(client, path);
68
+ if (!fn) {
69
+ throw new AgentSurfaceError({
70
+ code: "EXECUTION_FAILED",
71
+ message: "The server call failed.",
72
+ retry: "no",
73
+ details: { reason: "transport" }
74
+ });
75
+ }
76
+ return fn(input, {
77
+ signal: ctx.signal,
78
+ ...options.callContext ? { context: options.callContext(ctx) } : {}
79
+ });
80
+ }
81
+ };
82
+ node[leaf] = ref;
83
+ }
84
+ const executor = {
85
+ paths: Object.keys(manifest.tools),
86
+ async execute({ path, input, info }) {
87
+ const tool = manifest.tools[path];
88
+ const fn = walkClient(client, path);
89
+ if (!tool || !fn) {
90
+ throw new AgentSurfaceError({
91
+ code: "EXECUTION_FAILED",
92
+ message: "The server call failed.",
93
+ retry: "no",
94
+ details: { reason: "transport" }
95
+ });
96
+ }
97
+ try {
98
+ const output = await fn(input, {
99
+ signal: info.signal,
100
+ ...options.callContext ? { context: options.callContext(info) } : {}
101
+ });
102
+ return output;
103
+ } catch (error) {
104
+ const mapped = mapError(error) ?? defaultMapServerError(error);
105
+ if (mapped) throw new AgentSurfaceError(mapped, { cause: error });
106
+ throw new AgentSurfaceError(
107
+ {
108
+ code: "EXECUTION_FAILED",
109
+ message: "The server call failed.",
110
+ retry: isTransient(error) ? "after-delay" : "no",
111
+ details: {
112
+ reason: "transport",
113
+ ...isTransient(error) ? { transient: true, retryAfterMs: 1e3 } : {}
114
+ }
115
+ },
116
+ { cause: error }
117
+ );
118
+ }
119
+ }
120
+ };
121
+ return {
122
+ refs,
123
+ executor,
124
+ hasPath: (path) => path in manifest.tools,
125
+ manifest
126
+ };
127
+ }
128
+ function isTransient(error) {
129
+ if (error instanceof TypeError) return true;
130
+ return isRecord(error) && error.transient === true;
131
+ }
132
+ function isBridgeRef(value) {
133
+ return isRecord(value) && value[BRIDGE_REF] === true;
134
+ }
135
+
136
+ // src/binding.ts
137
+ function reduceInputSchema(full, boundKeys, overridable) {
138
+ const clone = JSON.parse(JSON.stringify(full));
139
+ const properties = clone.properties ?? {};
140
+ const lockedKeys = boundKeys.filter((k) => !overridable.has(k));
141
+ for (const key of lockedKeys) {
142
+ delete properties[key];
143
+ }
144
+ for (const key of boundKeys) {
145
+ if (!overridable.has(key)) continue;
146
+ const prop = properties[key];
147
+ if (typeof prop === "object" && prop !== null) {
148
+ const record = prop;
149
+ const note = "Defaults to the current UI value at execution time when omitted.";
150
+ record.description = typeof record.description === "string" && record.description.length > 0 ? `${record.description} ${note}` : note;
151
+ }
152
+ }
153
+ if (Array.isArray(clone.required)) {
154
+ const removed = new Set(boundKeys);
155
+ clone.required = clone.required.filter((k) => !removed.has(k));
156
+ if (clone.required.length === 0) delete clone.required;
157
+ }
158
+ if (Object.keys(properties).length === 0) {
159
+ return { type: "object", properties: {}, additionalProperties: false };
160
+ }
161
+ clone.properties = properties;
162
+ return clone;
163
+ }
164
+ function bindAgentProcedure(ref, config) {
165
+ let boundKeys = [];
166
+ if (config?.bind) {
167
+ try {
168
+ boundKeys = Object.keys(config.bind() ?? {});
169
+ } catch (err) {
170
+ console.warn(
171
+ `[agent-surface] bind() threw while capturing bound keys for ${ref.id}; treating as unbound`,
172
+ err
173
+ );
174
+ }
175
+ }
176
+ const overridable = /* @__PURE__ */ new Set([...config?.overridableFields ?? []]);
177
+ const lockedKeys = boundKeys.filter((k) => !overridable.has(k));
178
+ return {
179
+ kind: "procedure-binding",
180
+ ref: {
181
+ id: ref.id,
182
+ path: ref.path,
183
+ description: ref.description,
184
+ inputSchema: ref.inputSchema,
185
+ ...ref.outputSchema ? { outputSchema: ref.outputSchema } : {},
186
+ effect: ref.effect,
187
+ ...ref.requiresApproval !== void 0 ? { requiresApproval: ref.requiresApproval } : {}
188
+ },
189
+ config: {
190
+ ...config?.when ? { when: config.when } : {},
191
+ ...config?.unavailableReason !== void 0 ? { unavailableReason: config.unavailableReason } : {},
192
+ ...config?.bind ? { bind: config.bind } : {},
193
+ ...config?.overridableFields ? { overridableFields: config.overridableFields } : {},
194
+ ...config?.confirmation ? { confirmation: config.confirmation } : {},
195
+ ...config?.policies ? { policies: config.policies } : {},
196
+ ...config?.describe ? { describe: config.describe } : {},
197
+ ...config?.meta ? { meta: config.meta } : {}
198
+ },
199
+ boundKeys,
200
+ lockedKeys,
201
+ reducedInputSchema: reduceInputSchema(ref.inputSchema, boundKeys, overridable)
202
+ };
203
+ }
204
+
205
+ export {
206
+ createOrpcAgentBridge,
207
+ isBridgeRef,
208
+ reduceInputSchema,
209
+ bindAgentProcedure
210
+ };
211
+ //# sourceMappingURL=chunk-PIGF6NNJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/bridge.ts","../src/binding.ts"],"sourcesContent":["import {\n AgentSurfaceError,\n type AgentProcedureEffect,\n type AgentProcedureExecutor,\n type JsonSchema,\n type JsonValue,\n type ProcedureCallInfo,\n} from \"@agent-surface/core\";\n\n/**\n * [Experimental] Minimal contract this package needs from orpc-agent\n * (docs/05, OQ-1). Derivable from a build-time export of the capability\n * registry inventory or a bootstrap `runtime.describe()` fetch; hand-writing\n * it remains the escape hatch.\n */\nexport interface OrpcAgentManifest {\n tools: Record<\n string, // key: dot path, \"devices.disable\"\n {\n description: string;\n inputSchema: JsonSchema;\n outputSchema?: JsonSchema;\n effect: AgentProcedureEffect;\n /** Server-declared flags the client must respect (e.g. approval required). */\n requiresApproval?: boolean;\n }\n >;\n}\n\nexport interface AgentProcedureRef<TIn extends object, TOut> {\n readonly id: string; // \"domain:devices.disable\"\n readonly path: string; // \"devices.disable\"\n readonly description: string;\n readonly inputSchema: JsonSchema;\n readonly outputSchema?: JsonSchema;\n readonly effect: AgentProcedureEffect;\n readonly requiresApproval?: boolean;\n call(input: TIn, ctx: ProcedureCallInfo): Promise<TOut>;\n /** Phantom generics carrier (never read at runtime). */\n readonly __types?: { input: TIn; output: TOut };\n}\n\nexport const BRIDGE_REF: unique symbol = Symbol(\"agent-surface.orpc-ref\");\n\n/** oRPC-style typed client: nested records of callable procedures. */\nexport type AnyClientLeaf = (input: never, options?: unknown) => Promise<unknown>;\nexport interface ClientTree {\n [key: string]: AnyClientLeaf | ClientTree;\n}\n\nexport type RefsFor<TClient> = {\n [K in keyof TClient]: TClient[K] extends (input: infer I, ...rest: never[]) => Promise<infer O>\n ? AgentProcedureRef<I & object, O>\n : RefsFor<TClient[K]>;\n};\n\nexport interface OrpcAgentBridgeOptions<TClient extends object> {\n /** The app's existing typed oRPC client (the user's session transport). */\n client: TClient;\n /** Which procedures orpc-agent exposes — the exposure CEILING (docs/05). */\n manifest: OrpcAgentManifest;\n /** Forward confirmation evidence / metadata into the call context. */\n callContext?: (ctx: ProcedureCallInfo) => Record<string, unknown>;\n /** Escape hatch: map raw server errors to typed payloads. */\n mapServerError?: (\n error: unknown,\n ) => import(\"@agent-surface/core\").AgentCapabilityErrorPayload | undefined;\n}\n\nexport interface OrpcAgentBridge<TClient extends object> {\n /** Typed refs mirroring the router path — only manifest paths exist. */\n refs: RefsFor<TClient>;\n /** Install via registry.setProcedureExecutor(bridge.executor). */\n executor: AgentProcedureExecutor;\n hasPath(path: string): boolean;\n manifest: OrpcAgentManifest;\n}\n\nfunction walkClient(client: ClientTree, path: string): AnyClientLeaf | undefined {\n let node: ClientTree | AnyClientLeaf = client;\n for (const segment of path.split(\".\")) {\n if (typeof node !== \"object\" || node === null) return undefined;\n const next: ClientTree | AnyClientLeaf | undefined = (node as ClientTree)[segment];\n if (next === undefined) return undefined;\n node = next;\n }\n return typeof node === \"function\" ? node : undefined;\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null;\n}\n\nfunction defaultMapServerError(\n error: unknown,\n): import(\"@agent-surface/core\").AgentCapabilityErrorPayload | undefined {\n if (!isRecord(error)) return undefined;\n const code = error.code ?? error.status;\n if (code === \"UNAUTHORIZED\" || code === \"FORBIDDEN\" || code === 401 || code === 403) {\n return {\n code: \"NOT_AUTHORIZED\",\n message: \"The server rejected this call as not authorized.\",\n retry: \"no\",\n details: { origin: \"server\" },\n };\n }\n const data = isRecord(error.data) ? error.data : undefined;\n if (code === \"APPROVAL_REQUIRED\" || data?.approvalRequired === true) {\n return {\n code: \"CONFIRMATION_REQUIRED\",\n message:\n \"The server requires its own approval for this operation. Wait for approval, then retry.\",\n retry: \"with-confirmation\",\n details: {\n origin: \"server\",\n ...(typeof data?.approvalId === \"string\" ? { confirmationId: data.approvalId } : {}),\n },\n };\n }\n return undefined;\n}\n\n/**\n * Creates the manifest-gated bridge between the app's oRPC client and the\n * agent surface. The frontend can narrow domain exposure (by not\n * referencing) but can never widen it — the manifest is the ceiling.\n */\nexport function createOrpcAgentBridge<TClient extends object>(\n options: OrpcAgentBridgeOptions<TClient>,\n): OrpcAgentBridge<TClient> {\n const client = options.client as ClientTree;\n const { manifest } = options;\n const mapError = options.mapServerError ?? defaultMapServerError;\n\n const refs: Record<string, unknown> = {};\n for (const [path, tool] of Object.entries(manifest.tools)) {\n const segments = path.split(\".\");\n let node = refs;\n for (const segment of segments.slice(0, -1)) {\n node[segment] = node[segment] ?? {};\n node = node[segment] as Record<string, unknown>;\n }\n const leaf = segments[segments.length - 1]!;\n const ref: AgentProcedureRef<object, unknown> & { [BRIDGE_REF]: true } = {\n id: `domain:${path}`,\n path,\n description: tool.description,\n inputSchema: tool.inputSchema,\n ...(tool.outputSchema ? { outputSchema: tool.outputSchema } : {}),\n effect: tool.effect,\n ...(tool.requiresApproval !== undefined ? { requiresApproval: tool.requiresApproval } : {}),\n [BRIDGE_REF]: true,\n async call(input: object, ctx: ProcedureCallInfo): Promise<unknown> {\n const fn = walkClient(client, path);\n if (!fn) {\n throw new AgentSurfaceError({\n code: \"EXECUTION_FAILED\",\n message: \"The server call failed.\",\n retry: \"no\",\n details: { reason: \"transport\" },\n });\n }\n return fn(input as never, {\n signal: ctx.signal,\n ...(options.callContext ? { context: options.callContext(ctx) } : {}),\n });\n },\n };\n node[leaf] = ref;\n }\n\n const executor: AgentProcedureExecutor = {\n paths: Object.keys(manifest.tools),\n async execute({ path, input, info }): Promise<JsonValue> {\n const tool = manifest.tools[path];\n const fn = walkClient(client, path);\n if (!tool || !fn) {\n throw new AgentSurfaceError({\n code: \"EXECUTION_FAILED\",\n message: \"The server call failed.\",\n retry: \"no\",\n details: { reason: \"transport\" },\n });\n }\n try {\n const output = await fn(input as never, {\n signal: info.signal,\n ...(options.callContext ? { context: options.callContext(info) } : {}),\n });\n return output as JsonValue;\n } catch (error) {\n const mapped = mapError(error) ?? defaultMapServerError(error);\n if (mapped) throw new AgentSurfaceError(mapped, { cause: error });\n // Transport/procedure errors are SANITIZED (docs/05 step 5, docs/07):\n // never pass error.message through to the agent.\n throw new AgentSurfaceError(\n {\n code: \"EXECUTION_FAILED\",\n message: \"The server call failed.\",\n retry: isTransient(error) ? \"after-delay\" : \"no\",\n details: {\n reason: \"transport\",\n ...(isTransient(error) ? { transient: true, retryAfterMs: 1000 } : {}),\n },\n },\n { cause: error },\n );\n }\n },\n };\n\n return {\n refs: refs as RefsFor<TClient>,\n executor,\n hasPath: (path) => path in manifest.tools,\n manifest,\n };\n}\n\nfunction isTransient(error: unknown): boolean {\n if (error instanceof TypeError) return true; // fetch network failure shape\n return isRecord(error) && error.transient === true;\n}\n\nexport function isBridgeRef(value: unknown): value is AgentProcedureRef<object, unknown> {\n return isRecord(value) && (value as { [BRIDGE_REF]?: unknown })[BRIDGE_REF] === true;\n}\n","import type {\n AgentPolicy,\n AgentProcedureBinding,\n JsonSchema,\n JsonValue,\n} from \"@agent-surface/core\";\nimport type { AgentProcedureRef } from \"./bridge.js\";\n\nexport interface AgentProcedureBindingConfig<\n TIn extends object,\n TBound extends Partial<TIn>,\n> {\n /** Contextual availability; same semantics as capability `when`. */\n when?: () => boolean;\n unavailableReason?: string | (() => string);\n /**\n * UI-derived inputs. Evaluated at EXECUTION time (never cached from\n * discovery). Throwing or returning schema-invalid values fails the\n * invocation with PRECONDITION_FAILED (details.reason: \"binding-failed\").\n */\n bind?: () => TBound;\n /**\n * Bound fields the agent MAY override. Default: none — bound fields are\n * locked (D8). Use sparingly.\n */\n overridableFields?: ReadonlyArray<keyof TBound & string>;\n /** Escalate (never lower) the manifest's confirmation requirement. */\n confirmation?: \"optional\" | \"required\";\n /** Extra frontend policies (client-side, advisory). */\n policies?: AgentPolicy[];\n /** Contextual description appended to the manifest description. */\n describe?: () => string;\n meta?: Record<string, JsonValue>;\n}\n\n/**\n * D7 rule 1 — agent-facing schema surgery: locked bound keys are removed from\n * `properties` and `required`; overridable bound keys stay, annotated as\n * defaulting to the current UI value. All-bound ⇒ empty closed object schema.\n */\nexport function reduceInputSchema(\n full: JsonSchema,\n boundKeys: ReadonlyArray<string>,\n overridable: ReadonlySet<string>,\n): JsonSchema {\n const clone = JSON.parse(JSON.stringify(full)) as JsonSchema;\n const properties = (clone.properties ?? {}) as Record<string, unknown>;\n const lockedKeys = boundKeys.filter((k) => !overridable.has(k));\n\n for (const key of lockedKeys) {\n delete properties[key];\n }\n for (const key of boundKeys) {\n if (!overridable.has(key)) continue;\n const prop = properties[key];\n if (typeof prop === \"object\" && prop !== null) {\n const record = prop as Record<string, unknown>;\n const note = \"Defaults to the current UI value at execution time when omitted.\";\n record.description =\n typeof record.description === \"string\" && record.description.length > 0\n ? `${record.description} ${note}`\n : note;\n }\n }\n if (Array.isArray(clone.required)) {\n // Locked keys are supplied by the binding; overridable keys become\n // optional for the agent (the bound value applies when omitted).\n const removed = new Set(boundKeys);\n clone.required = (clone.required as string[]).filter((k) => !removed.has(k));\n if ((clone.required as string[]).length === 0) delete clone.required;\n }\n if (Object.keys(properties).length === 0) {\n return { type: \"object\", properties: {}, additionalProperties: false };\n }\n clone.properties = properties;\n return clone;\n}\n\n/**\n * Creates a procedure binding for AgentComponentDefinition.procedures or the\n * React hook. The binding's identity IS the procedure's identity — there is\n * deliberately no place to put an execute handler here (docs/05).\n */\nexport function bindAgentProcedure<\n TIn extends object,\n TOut,\n TBound extends Partial<TIn> = Partial<TIn>,\n>(\n ref: AgentProcedureRef<TIn, TOut>,\n config?: AgentProcedureBindingConfig<TIn, TBound>,\n): AgentProcedureBinding<TIn, TOut> {\n // Bound-key capture at binding creation: bind() must be key-stable; a\n // throwing bind() here degrades to \"no bound keys\" with a warning.\n let boundKeys: string[] = [];\n if (config?.bind) {\n try {\n boundKeys = Object.keys(config.bind() ?? {});\n } catch (err) {\n // eslint-disable-next-line no-console\n console.warn(\n `[agent-surface] bind() threw while capturing bound keys for ${ref.id}; treating as unbound`,\n err,\n );\n }\n }\n const overridable = new Set<string>([...(config?.overridableFields ?? [])]);\n const lockedKeys = boundKeys.filter((k) => !overridable.has(k));\n\n return {\n kind: \"procedure-binding\",\n ref: {\n id: ref.id,\n path: ref.path,\n description: ref.description,\n inputSchema: ref.inputSchema,\n ...(ref.outputSchema ? { outputSchema: ref.outputSchema } : {}),\n effect: ref.effect,\n ...(ref.requiresApproval !== undefined ? { requiresApproval: ref.requiresApproval } : {}),\n },\n config: {\n ...(config?.when ? { when: config.when } : {}),\n ...(config?.unavailableReason !== undefined\n ? { unavailableReason: config.unavailableReason }\n : {}),\n ...(config?.bind ? { bind: config.bind as () => Record<string, JsonValue> } : {}),\n ...(config?.overridableFields\n ? { overridableFields: config.overridableFields as ReadonlyArray<string> }\n : {}),\n ...(config?.confirmation ? { confirmation: config.confirmation } : {}),\n ...(config?.policies ? { policies: config.policies } : {}),\n ...(config?.describe ? { describe: config.describe } : {}),\n ...(config?.meta ? { meta: config.meta } : {}),\n },\n boundKeys,\n lockedKeys,\n reducedInputSchema: reduceInputSchema(ref.inputSchema, boundKeys, overridable),\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,106 @@
1
+ import * as _agent_surface_core from '@agent-surface/core';
2
+ import { JsonSchema, AgentProcedureEffect, ProcedureCallInfo, AgentProcedureExecutor, AgentPolicy, JsonValue, AgentProcedureBinding } from '@agent-surface/core';
3
+
4
+ /**
5
+ * [Experimental] Minimal contract this package needs from orpc-agent
6
+ * (docs/05, OQ-1). Derivable from a build-time export of the capability
7
+ * registry inventory or a bootstrap `runtime.describe()` fetch; hand-writing
8
+ * it remains the escape hatch.
9
+ */
10
+ interface OrpcAgentManifest {
11
+ tools: Record<string, // key: dot path, "devices.disable"
12
+ {
13
+ description: string;
14
+ inputSchema: JsonSchema;
15
+ outputSchema?: JsonSchema;
16
+ effect: AgentProcedureEffect;
17
+ /** Server-declared flags the client must respect (e.g. approval required). */
18
+ requiresApproval?: boolean;
19
+ }>;
20
+ }
21
+ interface AgentProcedureRef<TIn extends object, TOut> {
22
+ readonly id: string;
23
+ readonly path: string;
24
+ readonly description: string;
25
+ readonly inputSchema: JsonSchema;
26
+ readonly outputSchema?: JsonSchema;
27
+ readonly effect: AgentProcedureEffect;
28
+ readonly requiresApproval?: boolean;
29
+ call(input: TIn, ctx: ProcedureCallInfo): Promise<TOut>;
30
+ /** Phantom generics carrier (never read at runtime). */
31
+ readonly __types?: {
32
+ input: TIn;
33
+ output: TOut;
34
+ };
35
+ }
36
+ /** oRPC-style typed client: nested records of callable procedures. */
37
+ type AnyClientLeaf = (input: never, options?: unknown) => Promise<unknown>;
38
+ interface ClientTree {
39
+ [key: string]: AnyClientLeaf | ClientTree;
40
+ }
41
+ type RefsFor<TClient> = {
42
+ [K in keyof TClient]: TClient[K] extends (input: infer I, ...rest: never[]) => Promise<infer O> ? AgentProcedureRef<I & object, O> : RefsFor<TClient[K]>;
43
+ };
44
+ interface OrpcAgentBridgeOptions<TClient extends object> {
45
+ /** The app's existing typed oRPC client (the user's session transport). */
46
+ client: TClient;
47
+ /** Which procedures orpc-agent exposes — the exposure CEILING (docs/05). */
48
+ manifest: OrpcAgentManifest;
49
+ /** Forward confirmation evidence / metadata into the call context. */
50
+ callContext?: (ctx: ProcedureCallInfo) => Record<string, unknown>;
51
+ /** Escape hatch: map raw server errors to typed payloads. */
52
+ mapServerError?: (error: unknown) => _agent_surface_core.AgentCapabilityErrorPayload | undefined;
53
+ }
54
+ interface OrpcAgentBridge<TClient extends object> {
55
+ /** Typed refs mirroring the router path — only manifest paths exist. */
56
+ refs: RefsFor<TClient>;
57
+ /** Install via registry.setProcedureExecutor(bridge.executor). */
58
+ executor: AgentProcedureExecutor;
59
+ hasPath(path: string): boolean;
60
+ manifest: OrpcAgentManifest;
61
+ }
62
+ /**
63
+ * Creates the manifest-gated bridge between the app's oRPC client and the
64
+ * agent surface. The frontend can narrow domain exposure (by not
65
+ * referencing) but can never widen it — the manifest is the ceiling.
66
+ */
67
+ declare function createOrpcAgentBridge<TClient extends object>(options: OrpcAgentBridgeOptions<TClient>): OrpcAgentBridge<TClient>;
68
+ declare function isBridgeRef(value: unknown): value is AgentProcedureRef<object, unknown>;
69
+
70
+ interface AgentProcedureBindingConfig<TIn extends object, TBound extends Partial<TIn>> {
71
+ /** Contextual availability; same semantics as capability `when`. */
72
+ when?: () => boolean;
73
+ unavailableReason?: string | (() => string);
74
+ /**
75
+ * UI-derived inputs. Evaluated at EXECUTION time (never cached from
76
+ * discovery). Throwing or returning schema-invalid values fails the
77
+ * invocation with PRECONDITION_FAILED (details.reason: "binding-failed").
78
+ */
79
+ bind?: () => TBound;
80
+ /**
81
+ * Bound fields the agent MAY override. Default: none — bound fields are
82
+ * locked (D8). Use sparingly.
83
+ */
84
+ overridableFields?: ReadonlyArray<keyof TBound & string>;
85
+ /** Escalate (never lower) the manifest's confirmation requirement. */
86
+ confirmation?: "optional" | "required";
87
+ /** Extra frontend policies (client-side, advisory). */
88
+ policies?: AgentPolicy[];
89
+ /** Contextual description appended to the manifest description. */
90
+ describe?: () => string;
91
+ meta?: Record<string, JsonValue>;
92
+ }
93
+ /**
94
+ * D7 rule 1 — agent-facing schema surgery: locked bound keys are removed from
95
+ * `properties` and `required`; overridable bound keys stay, annotated as
96
+ * defaulting to the current UI value. All-bound ⇒ empty closed object schema.
97
+ */
98
+ declare function reduceInputSchema(full: JsonSchema, boundKeys: ReadonlyArray<string>, overridable: ReadonlySet<string>): JsonSchema;
99
+ /**
100
+ * Creates a procedure binding for AgentComponentDefinition.procedures or the
101
+ * React hook. The binding's identity IS the procedure's identity — there is
102
+ * deliberately no place to put an execute handler here (docs/05).
103
+ */
104
+ declare function bindAgentProcedure<TIn extends object, TOut, TBound extends Partial<TIn> = Partial<TIn>>(ref: AgentProcedureRef<TIn, TOut>, config?: AgentProcedureBindingConfig<TIn, TBound>): AgentProcedureBinding<TIn, TOut>;
105
+
106
+ export { type AgentProcedureBindingConfig, type AgentProcedureRef, type ClientTree, type OrpcAgentBridge, type OrpcAgentBridgeOptions, type OrpcAgentManifest, type RefsFor, bindAgentProcedure, createOrpcAgentBridge, isBridgeRef, reduceInputSchema };
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ import {
2
+ bindAgentProcedure,
3
+ createOrpcAgentBridge,
4
+ isBridgeRef,
5
+ reduceInputSchema
6
+ } from "./chunk-PIGF6NNJ.js";
7
+ export {
8
+ bindAgentProcedure,
9
+ createOrpcAgentBridge,
10
+ isBridgeRef,
11
+ reduceInputSchema
12
+ };
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,16 @@
1
+ import { AgentProcedureRef, AgentProcedureBindingConfig } from './index.js';
2
+ import '@agent-surface/core';
3
+
4
+ /**
5
+ * Declares that an EXISTING domain procedure is relevant in the current view,
6
+ * optionally pre-filling inputs from UI state (docs/05). Lifecycle mirrors
7
+ * useAgentComponent: registers in an effect, unregisters on unmount;
8
+ * bind/when/describe are read through the latest ref (fresh at execution);
9
+ * availability is pushed on change.
10
+ *
11
+ * A ref not backed by the manifest registers NOTHING — the manifest, produced
12
+ * by the backend's orpc-agent configuration, is the exposure ceiling.
13
+ */
14
+ declare function useAgentProcedure<TIn extends object, TOut, TBound extends Partial<TIn> = Partial<TIn>>(ref: AgentProcedureRef<TIn, TOut>, config?: AgentProcedureBindingConfig<TIn, TBound>): void;
15
+
16
+ export { useAgentProcedure };
package/dist/react.js ADDED
@@ -0,0 +1,109 @@
1
+ import {
2
+ bindAgentProcedure,
3
+ isBridgeRef
4
+ } from "./chunk-PIGF6NNJ.js";
5
+
6
+ // src/react.ts
7
+ import { useEffect, useRef, useState } from "react";
8
+ import {
9
+ useAgentSurface,
10
+ unstable_readRenderScopeContext
11
+ } from "@agent-surface/react";
12
+ var hookInstanceCounter = 0;
13
+ function useAgentProcedure(ref, config) {
14
+ const registry = useAgentSurface();
15
+ const latestConfig = useRef(config);
16
+ latestConfig.current = config;
17
+ const contextLink = unstable_readRenderScopeContext();
18
+ const contextRef = useRef(contextLink);
19
+ contextRef.current = contextLink ?? contextRef.current;
20
+ const instanceRef = useRef(null);
21
+ if (instanceRef.current === null) {
22
+ hookInstanceCounter += 1;
23
+ instanceRef.current = `ref-${hookInstanceCounter}`;
24
+ }
25
+ const handleRef = useRef(null);
26
+ const lastPushed = useRef(null);
27
+ const [, setStatus] = useState("pending");
28
+ const manifestBacked = isBridgeRef(ref);
29
+ const refId = ref?.id;
30
+ useEffect(() => {
31
+ if (!manifestBacked) {
32
+ console.error(
33
+ `[agent-surface] useAgentProcedure: ref "${String(refId)}" is not backed by the orpc-agent manifest \u2014 registering nothing. The manifest is the exposure ceiling.`
34
+ );
35
+ return;
36
+ }
37
+ const delegating = {
38
+ when: () => {
39
+ const when = latestConfig.current?.when;
40
+ return when ? when() !== false : true;
41
+ },
42
+ unavailableReason: () => {
43
+ const reason = latestConfig.current?.unavailableReason;
44
+ try {
45
+ if (typeof reason === "function") return reason();
46
+ if (typeof reason === "string") return reason;
47
+ } catch {
48
+ }
49
+ return "Currently unavailable";
50
+ },
51
+ ...latestConfig.current?.bind ? { bind: () => latestConfig.current?.bind?.() ?? {} } : {},
52
+ ...latestConfig.current?.overridableFields ? { overridableFields: latestConfig.current.overridableFields } : {},
53
+ ...latestConfig.current?.confirmation ? { confirmation: latestConfig.current.confirmation } : {},
54
+ ...latestConfig.current?.policies ? { policies: latestConfig.current.policies } : {},
55
+ ...latestConfig.current?.describe ? { describe: () => latestConfig.current?.describe?.() ?? "" } : {},
56
+ ...latestConfig.current?.meta ? { meta: latestConfig.current.meta } : {}
57
+ };
58
+ const binding = bindAgentProcedure(ref, delegating);
59
+ if (contextRef.current) binding.contextLink = { ...contextRef.current };
60
+ const handle = registry.register({
61
+ // Procedure-only registration: excluded from snapshot.components.
62
+ type: "orpc-ref",
63
+ instanceId: instanceRef.current,
64
+ description: `Contextual reference to ${ref.path}`,
65
+ procedures: [binding]
66
+ });
67
+ handleRef.current = handle;
68
+ lastPushed.current = null;
69
+ setStatus(handle.status === "active" ? "active" : "rejected");
70
+ return () => {
71
+ handle.unregister();
72
+ handleRef.current = null;
73
+ };
74
+ }, [registry, refId, manifestBacked]);
75
+ useEffect(() => {
76
+ const handle = handleRef.current;
77
+ if (!handle || handle.status !== "active" || !manifestBacked) return;
78
+ const when = latestConfig.current?.when;
79
+ let available = true;
80
+ try {
81
+ available = when ? when() !== false : true;
82
+ } catch {
83
+ available = false;
84
+ }
85
+ if (lastPushed.current === available) return;
86
+ lastPushed.current = available;
87
+ const reason = latestConfig.current?.unavailableReason;
88
+ let reasonText;
89
+ if (!available) {
90
+ try {
91
+ reasonText = typeof reason === "function" ? reason() : reason;
92
+ } catch {
93
+ reasonText = void 0;
94
+ }
95
+ }
96
+ handle.update({
97
+ availability: {
98
+ [ref.path]: {
99
+ available,
100
+ ...reasonText !== void 0 ? { reason: reasonText } : {}
101
+ }
102
+ }
103
+ });
104
+ });
105
+ }
106
+ export {
107
+ useAgentProcedure
108
+ };
109
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/react.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport type { AgentRegistrationHandle } from \"@agent-surface/core\";\nimport {\n useAgentSurface,\n unstable_readRenderScopeContext,\n} from \"@agent-surface/react\";\nimport type { AgentProcedureRef } from \"./bridge.js\";\nimport { isBridgeRef } from \"./bridge.js\";\nimport { bindAgentProcedure, type AgentProcedureBindingConfig } from \"./binding.js\";\n\nlet hookInstanceCounter = 0;\n\n/**\n * Declares that an EXISTING domain procedure is relevant in the current view,\n * optionally pre-filling inputs from UI state (docs/05). Lifecycle mirrors\n * useAgentComponent: registers in an effect, unregisters on unmount;\n * bind/when/describe are read through the latest ref (fresh at execution);\n * availability is pushed on change.\n *\n * A ref not backed by the manifest registers NOTHING — the manifest, produced\n * by the backend's orpc-agent configuration, is the exposure ceiling.\n */\nexport function useAgentProcedure<\n TIn extends object,\n TOut,\n TBound extends Partial<TIn> = Partial<TIn>,\n>(ref: AgentProcedureRef<TIn, TOut>, config?: AgentProcedureBindingConfig<TIn, TBound>): void {\n const registry = useAgentSurface();\n\n const latestConfig = useRef(config);\n latestConfig.current = config;\n\n // Capture the render-scope link (owning useAgentComponent, when present)\n // during render; the registration effect uses the captured value.\n const contextLink = unstable_readRenderScopeContext();\n const contextRef = useRef(contextLink);\n contextRef.current = contextLink ?? contextRef.current;\n\n const instanceRef = useRef<string | null>(null);\n if (instanceRef.current === null) {\n hookInstanceCounter += 1;\n instanceRef.current = `ref-${hookInstanceCounter}`;\n }\n\n const handleRef = useRef<AgentRegistrationHandle | null>(null);\n const lastPushed = useRef<boolean | null>(null);\n const [, setStatus] = useState<\"pending\" | \"active\" | \"rejected\">(\"pending\");\n\n const manifestBacked = isBridgeRef(ref);\n const refId = ref?.id;\n\n useEffect(() => {\n if (!manifestBacked) {\n // Exposure gating (docs/05): register nothing, say so loudly.\n // eslint-disable-next-line no-console\n console.error(\n `[agent-surface] useAgentProcedure: ref \"${String(refId)}\" is not backed by the orpc-agent manifest — registering nothing. The manifest is the exposure ceiling.`,\n );\n return;\n }\n\n const delegating: AgentProcedureBindingConfig<TIn, TBound> = {\n when: () => {\n const when = latestConfig.current?.when;\n return when ? when() !== false : true;\n },\n unavailableReason: () => {\n const reason = latestConfig.current?.unavailableReason;\n try {\n if (typeof reason === \"function\") return reason();\n if (typeof reason === \"string\") return reason;\n } catch {\n /* fall through */\n }\n return \"Currently unavailable\";\n },\n ...(latestConfig.current?.bind\n ? { bind: () => (latestConfig.current?.bind?.() ?? {}) as TBound }\n : {}),\n ...(latestConfig.current?.overridableFields\n ? { overridableFields: latestConfig.current.overridableFields }\n : {}),\n ...(latestConfig.current?.confirmation\n ? { confirmation: latestConfig.current.confirmation }\n : {}),\n ...(latestConfig.current?.policies ? { policies: latestConfig.current.policies } : {}),\n ...(latestConfig.current?.describe\n ? { describe: () => latestConfig.current?.describe?.() ?? \"\" }\n : {}),\n ...(latestConfig.current?.meta ? { meta: latestConfig.current.meta } : {}),\n };\n\n const binding = bindAgentProcedure(ref, delegating);\n if (contextRef.current) binding.contextLink = { ...contextRef.current };\n\n const handle = registry.register({\n // Procedure-only registration: excluded from snapshot.components.\n type: \"orpc-ref\",\n instanceId: instanceRef.current!,\n description: `Contextual reference to ${ref.path}`,\n procedures: [binding],\n });\n handleRef.current = handle;\n lastPushed.current = null;\n setStatus(handle.status === \"active\" ? \"active\" : \"rejected\");\n\n return () => {\n handle.unregister();\n handleRef.current = null;\n };\n }, [registry, refId, manifestBacked]);\n\n // Push availability when the `when` predicate flips (per commit).\n useEffect(() => {\n const handle = handleRef.current;\n if (!handle || handle.status !== \"active\" || !manifestBacked) return;\n const when = latestConfig.current?.when;\n let available = true;\n try {\n available = when ? when() !== false : true;\n } catch {\n available = false;\n }\n if (lastPushed.current === available) return;\n lastPushed.current = available;\n const reason = latestConfig.current?.unavailableReason;\n let reasonText: string | undefined;\n if (!available) {\n try {\n reasonText = typeof reason === \"function\" ? reason() : reason;\n } catch {\n reasonText = undefined;\n }\n }\n handle.update({\n availability: {\n [ref.path]: {\n available,\n ...(reasonText !== undefined ? { reason: reasonText } : {}),\n },\n },\n });\n });\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,92 @@
1
+ {
2
+ "name": "@agent-surface/orpc",
3
+ "version": "0.1.0",
4
+ "description": "Contextual references to oRPC domain procedures exposed via orpc-agent — binding, gating, executor bridge",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ },
15
+ "./react": {
16
+ "types": "./dist/react.d.ts",
17
+ "import": "./dist/react.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "LICENSE"
23
+ ],
24
+ "dependencies": {
25
+ "@agent-surface/core": "^0.1.0"
26
+ },
27
+ "peerDependencies": {
28
+ "react": ">=18.2",
29
+ "@agent-surface/react": "^0.1.0"
30
+ },
31
+ "peerDependenciesMeta": {
32
+ "@agent-surface/react": {
33
+ "optional": true
34
+ },
35
+ "react": {
36
+ "optional": true
37
+ }
38
+ },
39
+ "devDependencies": {
40
+ "react": "^19.1.1",
41
+ "react-dom": "^19.1.1",
42
+ "@types/react": "^19.1.9",
43
+ "@testing-library/react": "^16.3.0",
44
+ "zod": "^4.1.5",
45
+ "@agent-surface/react": "0.1.0",
46
+ "@agent-surface/testing": "0.1.0"
47
+ },
48
+ "author": "Paolo Barbato",
49
+ "engines": {
50
+ "node": ">=20.19.0"
51
+ },
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "git+https://github.com/Wiseair-srl/agent-surface.git",
55
+ "directory": "packages/orpc"
56
+ },
57
+ "homepage": "https://agent-surface-docs.vercel.app",
58
+ "bugs": {
59
+ "url": "https://github.com/Wiseair-srl/agent-surface/issues"
60
+ },
61
+ "publishConfig": {
62
+ "access": "public"
63
+ },
64
+ "keywords": [
65
+ "agent-surface",
66
+ "agent",
67
+ "ai",
68
+ "llm",
69
+ "frontend",
70
+ "capabilities",
71
+ "typescript",
72
+ "orpc",
73
+ "orpc-agent",
74
+ "rpc"
75
+ ],
76
+ "size-limit": [
77
+ {
78
+ "path": "dist/index.js",
79
+ "limit": "8 kB",
80
+ "ignore": [
81
+ "@agent-surface/core",
82
+ "@agent-surface/react",
83
+ "react"
84
+ ]
85
+ }
86
+ ],
87
+ "scripts": {
88
+ "build": "tsup",
89
+ "typecheck": "tsc --noEmit",
90
+ "size": "size-limit"
91
+ }
92
+ }