@agent-surface/orpc 0.15.0 → 0.17.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/README.md CHANGED
@@ -1,40 +1,12 @@
1
- # @agent-surface/orpc
1
+ # `@agent-surface/orpc`
2
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
- ```
3
+ Bind compiler-declared domain procedures to authoritative oRPC client paths.
24
4
 
25
5
  ```tsx
26
- import { useAgentProcedure } from "@agent-surface/orpc/react";
27
-
28
- useAgentProcedure(bridge.refs.devices.disable, {
6
+ useAgentProcedure(devicesDisableContract, bridge.refs.devices.disable, {
7
+ bind: () => ({ deviceIds: selectedIds }),
29
8
  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
9
  });
34
10
  ```
35
11
 
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.
12
+ The compiled contract owns identity/schemas/effect/confirmation. The bridge owns execution and server error mapping. See [oRPC integration](../../docs/05-orpc-integration.md).
@@ -133,79 +133,8 @@ function isBridgeRef(value) {
133
133
  return isRecord(value) && value[BRIDGE_REF] === true;
134
134
  }
135
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
136
  export {
206
137
  createOrpcAgentBridge,
207
- isBridgeRef,
208
- reduceInputSchema,
209
- bindAgentProcedure
138
+ isBridgeRef
210
139
  };
211
- //# sourceMappingURL=chunk-PIGF6NNJ.js.map
140
+ //# sourceMappingURL=chunk-BPVM2AJ4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/bridge.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"],"mappings":"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","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _agent_surface_core from '@agent-surface/core';
2
- import { JsonSchema, AgentProcedureEffect, ProcedureCallInfo, AgentProcedureExecutor, AgentPolicy, JsonValue, AgentProcedureBinding } from '@agent-surface/core';
2
+ import { JsonSchema, AgentProcedureEffect, ProcedureCallInfo, AgentProcedureExecutor, AgentPolicy, JsonValue } from '@agent-surface/core';
3
3
 
4
4
  /**
5
5
  * [Experimental] Minimal contract this package needs from orpc-agent
@@ -90,17 +90,5 @@ interface AgentProcedureBindingConfig<TIn extends object, TBound extends Partial
90
90
  describe?: () => string;
91
91
  meta?: Record<string, JsonValue>;
92
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
93
 
106
- export { type AgentProcedureBindingConfig, type AgentProcedureRef, type ClientTree, type OrpcAgentBridge, type OrpcAgentBridgeOptions, type OrpcAgentManifest, type RefsFor, bindAgentProcedure, createOrpcAgentBridge, isBridgeRef, reduceInputSchema };
94
+ export { type AgentProcedureBindingConfig, type AgentProcedureRef, type ClientTree, type OrpcAgentBridge, type OrpcAgentBridgeOptions, type OrpcAgentManifest, type RefsFor, createOrpcAgentBridge, isBridgeRef };
package/dist/index.js CHANGED
@@ -1,13 +1,9 @@
1
1
  import {
2
- bindAgentProcedure,
3
2
  createOrpcAgentBridge,
4
- isBridgeRef,
5
- reduceInputSchema
6
- } from "./chunk-PIGF6NNJ.js";
3
+ isBridgeRef
4
+ } from "./chunk-BPVM2AJ4.js";
7
5
  export {
8
- bindAgentProcedure,
9
6
  createOrpcAgentBridge,
10
- isBridgeRef,
11
- reduceInputSchema
7
+ isBridgeRef
12
8
  };
13
9
  //# sourceMappingURL=index.js.map
package/dist/react.d.ts CHANGED
@@ -1,5 +1,5 @@
1
+ import { JsonValue, AgentProcedureContract } from '@agent-surface/core';
1
2
  import { AgentProcedureRef, AgentProcedureBindingConfig } from './index.js';
2
- import '@agent-surface/core';
3
3
 
4
4
  /**
5
5
  * Declares that an EXISTING domain procedure is relevant in the current view,
@@ -11,6 +11,6 @@ import '@agent-surface/core';
11
11
  * A ref not backed by the manifest registers NOTHING — the manifest, produced
12
12
  * by the backend's orpc-agent configuration, is the exposure ceiling.
13
13
  */
14
- declare function useAgentProcedure<TIn extends object, TOut, TBound extends Partial<TIn> = Partial<TIn>>(ref: AgentProcedureRef<TIn, TOut>, config?: AgentProcedureBindingConfig<TIn, TBound>): void;
14
+ declare function useAgentProcedure<TIn extends Record<string, JsonValue>, TOut extends JsonValue, TBound extends Partial<TIn> = Partial<TIn>>(contract: AgentProcedureContract<TIn, TOut>, ref: AgentProcedureRef<TIn, TOut>, config?: AgentProcedureBindingConfig<TIn, TBound>): void;
15
15
 
16
16
  export { useAgentProcedure };
package/dist/react.js CHANGED
@@ -1,16 +1,92 @@
1
1
  import {
2
- bindAgentProcedure,
3
2
  isBridgeRef
4
- } from "./chunk-PIGF6NNJ.js";
3
+ } from "./chunk-BPVM2AJ4.js";
5
4
 
6
5
  // src/react.ts
7
6
  import { useEffect, useRef, useState } from "react";
7
+ import {
8
+ authorizeAgentProcedureBinding
9
+ } from "@agent-surface/core";
8
10
  import {
9
11
  useAgentSurface,
10
12
  unstable_readRenderScopeContext
11
13
  } from "@agent-surface/react";
14
+
15
+ // src/binding.ts
16
+ function reduceInputSchema(full, boundKeys, overridable) {
17
+ const clone = JSON.parse(JSON.stringify(full));
18
+ const properties = clone.properties ?? {};
19
+ const lockedKeys = boundKeys.filter((k) => !overridable.has(k));
20
+ for (const key of lockedKeys) {
21
+ delete properties[key];
22
+ }
23
+ for (const key of boundKeys) {
24
+ if (!overridable.has(key)) continue;
25
+ const prop = properties[key];
26
+ if (typeof prop === "object" && prop !== null) {
27
+ const record = prop;
28
+ const note = "Defaults to the current UI value at execution time when omitted.";
29
+ record.description = typeof record.description === "string" && record.description.length > 0 ? `${record.description} ${note}` : note;
30
+ }
31
+ }
32
+ if (Array.isArray(clone.required)) {
33
+ const removed = new Set(boundKeys);
34
+ clone.required = clone.required.filter((k) => !removed.has(k));
35
+ if (clone.required.length === 0) delete clone.required;
36
+ }
37
+ if (Object.keys(properties).length === 0) {
38
+ return { type: "object", properties: {}, additionalProperties: false };
39
+ }
40
+ clone.properties = properties;
41
+ return clone;
42
+ }
43
+ function bindAgentProcedure(ref, config) {
44
+ let boundKeys = [];
45
+ if (config?.bind) {
46
+ try {
47
+ boundKeys = Object.keys(config.bind() ?? {});
48
+ } catch (err) {
49
+ console.warn(
50
+ `[agent-surface] bind() threw while capturing bound keys for ${ref.id}; treating as unbound`,
51
+ err
52
+ );
53
+ }
54
+ }
55
+ const overridable = /* @__PURE__ */ new Set([...config?.overridableFields ?? []]);
56
+ const lockedKeys = boundKeys.filter((k) => !overridable.has(k));
57
+ return {
58
+ kind: "procedure-binding",
59
+ ref: {
60
+ id: ref.id,
61
+ path: ref.path,
62
+ description: ref.description,
63
+ inputSchema: ref.inputSchema,
64
+ ...ref.outputSchema ? { outputSchema: ref.outputSchema } : {},
65
+ effect: ref.effect,
66
+ ...ref.requiresApproval !== void 0 ? { requiresApproval: ref.requiresApproval } : {}
67
+ },
68
+ config: {
69
+ ...config?.when ? { when: config.when } : {},
70
+ ...config?.unavailableReason !== void 0 ? { unavailableReason: config.unavailableReason } : {},
71
+ ...config?.bind ? { bind: config.bind } : {},
72
+ ...config?.overridableFields ? { overridableFields: config.overridableFields } : {},
73
+ ...config?.confirmation ? { confirmation: config.confirmation } : {},
74
+ ...config?.policies ? { policies: config.policies } : {},
75
+ ...config?.describe ? { describe: config.describe } : {},
76
+ ...config?.meta ? { meta: config.meta } : {}
77
+ },
78
+ boundKeys,
79
+ lockedKeys,
80
+ reducedInputSchema: reduceInputSchema(ref.inputSchema, boundKeys, overridable)
81
+ };
82
+ }
83
+
84
+ // src/react.ts
12
85
  var hookInstanceCounter = 0;
13
- function useAgentProcedure(ref, config) {
86
+ function useAgentProcedure(contract, ref, config) {
87
+ useAgentProcedureDefinition(contract, ref, config);
88
+ }
89
+ function useAgentProcedureDefinition(contract, ref, config) {
14
90
  const registry = useAgentSurface();
15
91
  const latestConfig = useRef(config);
16
92
  latestConfig.current = config;
@@ -57,13 +133,15 @@ function useAgentProcedure(ref, config) {
57
133
  };
58
134
  const binding = bindAgentProcedure(ref, delegating);
59
135
  if (contextRef.current) binding.contextLink = { ...contextRef.current };
60
- const handle = registry.register({
136
+ const definition = {
61
137
  // Procedure-only registration: excluded from snapshot.components.
62
138
  type: "orpc-ref",
63
139
  instanceId: instanceRef.current,
64
140
  description: `Contextual reference to ${ref.path}`,
65
141
  procedures: [binding]
66
- });
142
+ };
143
+ const authorized = contract ? authorizeAgentProcedureBinding(contract, definition) : definition;
144
+ const handle = registry.register(authorized);
67
145
  handleRef.current = handle;
68
146
  lastPushed.current = null;
69
147
  setStatus(handle.status === "active" ? "active" : "rejected");
@@ -71,7 +149,7 @@ function useAgentProcedure(ref, config) {
71
149
  handle.unregister();
72
150
  handleRef.current = null;
73
151
  };
74
- }, [registry, refId, manifestBacked]);
152
+ }, [registry, refId, manifestBacked, contract]);
75
153
  useEffect(() => {
76
154
  const handle = handleRef.current;
77
155
  if (!handle || handle.status !== "active" || !manifestBacked) return;
package/dist/react.js.map CHANGED
@@ -1 +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":[]}
1
+ {"version":3,"sources":["../src/react.ts","../src/binding.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport {\n authorizeAgentProcedureBinding,\n} from \"@agent-surface/core\";\nimport type {\n AgentComponentDefinition,\n AgentProcedureContract,\n AgentRegistrationHandle,\n JsonValue,\n} 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 Record<string, JsonValue>,\n TOut extends JsonValue,\n TBound extends Partial<TIn> = Partial<TIn>,\n>(\n contract: AgentProcedureContract<TIn, TOut>,\n ref: AgentProcedureRef<TIn, TOut>,\n config?: AgentProcedureBindingConfig<TIn, TBound>,\n): void {\n useAgentProcedureDefinition(contract, ref, config);\n}\n\nfunction useAgentProcedureDefinition(\n contract: AgentProcedureContract<any, any> | undefined,\n ref: AgentProcedureRef<any, any>,\n config: AgentProcedureBindingConfig<any, any> | undefined,\n): 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<any, any> = {\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?.() ?? {} }\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 definition: AgentComponentDefinition = {\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 const authorized = contract ? authorizeAgentProcedureBinding(contract, definition) : definition;\n const handle = registry.register(authorized);\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, contract]);\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","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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-surface/orpc",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "description": "Contextual references to oRPC domain procedures exposed via orpc-agent — binding, gating, executor bridge",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -22,7 +22,7 @@
22
22
  "LICENSE"
23
23
  ],
24
24
  "dependencies": {
25
- "@agent-surface/core": "^0.15.0"
25
+ "@agent-surface/core": "^0.17.0"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "@agent-surface/react": ">=0.1.0",
@@ -42,8 +42,8 @@
42
42
  "@types/react": "^19.1.9",
43
43
  "@testing-library/react": "^16.3.0",
44
44
  "zod": "^4.1.5",
45
- "@agent-surface/react": "0.15.0",
46
- "@agent-surface/testing": "0.15.0"
45
+ "@agent-surface/react": "0.17.0",
46
+ "@agent-surface/testing": "0.17.0"
47
47
  },
48
48
  "author": "Paolo Barbato",
49
49
  "engines": {
@@ -1 +0,0 @@
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":[]}