@agent-surface/core 0.2.0 → 0.4.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 +2 -1
- package/dist/index.d.ts +113 -8
- package/dist/index.js +208 -60
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -40,7 +40,8 @@ registry.register(
|
|
|
40
40
|
|
|
41
41
|
const toolset = createAgentToolset(registry, {
|
|
42
42
|
consumer: { id: "copilot", kind: "embedded" },
|
|
43
|
-
|
|
43
|
+
topology: "embedded", // required (D26): embedded → confirmations "wait",
|
|
44
|
+
}); // remote → "two-phase". No global default.
|
|
44
45
|
```
|
|
45
46
|
|
|
46
47
|
React apps should use [`@agent-surface/react`](https://www.npmjs.com/package/@agent-surface/react) instead of calling `register` directly. Full specification: [docs](https://github.com/Wiseair-srl/agent-surface/tree/main/docs).
|
package/dist/index.d.ts
CHANGED
|
@@ -136,13 +136,41 @@ declare function encodeWireName(id: string): string;
|
|
|
136
136
|
* Wire name disambiguated per instance: providers require UNIQUE tool names,
|
|
137
137
|
* so when several live instances expose the same capability the adapter
|
|
138
138
|
* appends `_at_<instanceId>` (docs/09 rule 7 — the id↔name map stays
|
|
139
|
-
* authoritative
|
|
140
|
-
*
|
|
139
|
+
* authoritative).
|
|
140
|
+
*
|
|
141
|
+
* The result is ALWAYS ≤ 64 characters (`AS-WIRE-004`) and deterministic for a
|
|
142
|
+
* given `(id, instanceId, level)` (`AS-WIRE-005`). `level` escalates the hash
|
|
143
|
+
* when a catalog would otherwise emit the same name twice — see
|
|
144
|
+
* {@link assignWireNames}, which owns that check; callers with a whole catalog
|
|
145
|
+
* in hand should use it rather than this function directly.
|
|
146
|
+
*/
|
|
147
|
+
declare function encodeWireNameForInstance(id: string, instanceId?: string, level?: number): string;
|
|
148
|
+
interface WireNameEntry {
|
|
149
|
+
/** Canonical capability id. */
|
|
150
|
+
id: string;
|
|
151
|
+
/** Instance disambiguator, when several live instances share the id. */
|
|
152
|
+
instanceId?: string;
|
|
153
|
+
}
|
|
154
|
+
interface WireNameAssignment {
|
|
155
|
+
/** Emitted names, positionally aligned with the input entries. */
|
|
156
|
+
names: string[];
|
|
157
|
+
/** wireName → canonical id. Authoritative; shortened names are not decodable. */
|
|
158
|
+
byName: ReadonlyMap<string, string>;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Assigns wire names to a whole catalog, guaranteeing uniqueness within it
|
|
162
|
+
* (`AS-WIRE-006`). Two distinct entries that collide are BOTH re-encoded at the
|
|
163
|
+
* next hash level, so the outcome depends on the set of entries and not on
|
|
164
|
+
* their order (`AS-WIRE-005`). Escalation is bounded; the last level appends
|
|
165
|
+
* the entry's rank among the colliding keys, which terminates by construction.
|
|
141
166
|
*/
|
|
142
|
-
declare function
|
|
167
|
+
declare function assignWireNames(entries: readonly WireNameEntry[]): WireNameAssignment;
|
|
143
168
|
/**
|
|
144
|
-
* Reverses `encodeWireName` for
|
|
145
|
-
*
|
|
169
|
+
* Reverses `encodeWireName` for names that were encoded faithfully, and returns
|
|
170
|
+
* `undefined` for every name that was not — shortened names and per-instance
|
|
171
|
+
* names among them (`AS-WIRE-007`: consult `toolset.wireNameMap()` instead).
|
|
172
|
+
* Returning a plausible-but-wrong canonical id would take the audit identity
|
|
173
|
+
* with it, so this refuses anything it cannot re-encode byte-identically.
|
|
146
174
|
*/
|
|
147
175
|
declare function decodeWireName(name: string): string | undefined;
|
|
148
176
|
|
|
@@ -671,6 +699,15 @@ interface AgentSurfaceSnapshot {
|
|
|
671
699
|
truncated?: {
|
|
672
700
|
droppedComponents: number;
|
|
673
701
|
};
|
|
702
|
+
/**
|
|
703
|
+
* [Experimental] Present iff a configured scope floor refused part of a
|
|
704
|
+
* requested scope (D27) — set by the adapter, never by `snapshot()`, which
|
|
705
|
+
* has no floor to intersect against. Empty `components` alongside this marker
|
|
706
|
+
* means the request fell outside the floor, not that the surface is empty.
|
|
707
|
+
*/
|
|
708
|
+
scopeRejected?: {
|
|
709
|
+
prefixes: string[];
|
|
710
|
+
};
|
|
674
711
|
}
|
|
675
712
|
interface AgentComponentDescriptor {
|
|
676
713
|
type: string;
|
|
@@ -710,7 +747,20 @@ interface AgentActionDescriptor {
|
|
|
710
747
|
}
|
|
711
748
|
interface AgentProcedureDescriptor {
|
|
712
749
|
procedureId: string;
|
|
750
|
+
/**
|
|
751
|
+
* The manifest description. Stable across snapshots — the contextual
|
|
752
|
+
* `describe()` output is `contextualNote`, not part of this string, unless
|
|
753
|
+
* the registry was created with `snapshotMergesContextualNote: true`
|
|
754
|
+
* (the 0.2 default, removed in a later minor — D28).
|
|
755
|
+
*/
|
|
713
756
|
description: string;
|
|
757
|
+
/**
|
|
758
|
+
* Volatile: this snapshot's contextual `describe()` output, if any. Always
|
|
759
|
+
* populated, in both merge modes, so a host can migrate before the default
|
|
760
|
+
* moves. Use {@link stableDescriptionOf} to recover the note-free
|
|
761
|
+
* description without parsing.
|
|
762
|
+
*/
|
|
763
|
+
contextualNote?: string;
|
|
714
764
|
/** Agent-facing (reduced) input schema per binding rule 1 (docs/05). */
|
|
715
765
|
inputSchema: JsonSchema;
|
|
716
766
|
outputSchema?: JsonSchema;
|
|
@@ -733,6 +783,15 @@ interface AgentProcedureDescriptor {
|
|
|
733
783
|
meta?: Record<string, JsonValue>;
|
|
734
784
|
}
|
|
735
785
|
type AgentCapabilityDescriptorUnion = AgentObservationDescriptor | AgentActionDescriptor | AgentProcedureDescriptor;
|
|
786
|
+
/**
|
|
787
|
+
* The note-free description of a descriptor, whichever way the registry was
|
|
788
|
+
* configured to compose it (D28). The merge rule is `${description} ${note}`,
|
|
789
|
+
* so the split is exact — no host ever has to parse a prefix it did not write.
|
|
790
|
+
*/
|
|
791
|
+
declare function stableDescriptionOf(descriptor: {
|
|
792
|
+
description: string;
|
|
793
|
+
contextualNote?: string;
|
|
794
|
+
}): string;
|
|
736
795
|
|
|
737
796
|
interface RegistrationCandidate {
|
|
738
797
|
definition: AgentComponentDefinition;
|
|
@@ -756,6 +815,15 @@ interface RegistryOptions {
|
|
|
756
815
|
/** Route descriptor for snapshots (host wires its router here). */
|
|
757
816
|
route?: () => AgentRouteInfo | undefined;
|
|
758
817
|
limits?: Partial<AgentSurfaceLimits>;
|
|
818
|
+
/**
|
|
819
|
+
* D28 compatibility flag. `true` (default, for one minor) folds a procedure
|
|
820
|
+
* reference's contextual `describe()` output into
|
|
821
|
+
* `AgentProcedureDescriptor.description`, as 0.1 did. `false` keeps the two
|
|
822
|
+
* apart, so `description` is stable across snapshots and the live text is
|
|
823
|
+
* read from `contextualNote`. Populated either way; the default flips in a
|
|
824
|
+
* later minor and the flag is then removed.
|
|
825
|
+
*/
|
|
826
|
+
snapshotMergesContextualNote?: boolean;
|
|
759
827
|
/** Injectable clock (docs/08 determinism); default Date.now. */
|
|
760
828
|
now?: () => number;
|
|
761
829
|
}
|
|
@@ -791,7 +859,12 @@ declare function createAgentSurfaceRegistry(options?: RegistryOptions): AgentSur
|
|
|
791
859
|
|
|
792
860
|
interface AgentToolsetOptions {
|
|
793
861
|
consumer: AgentConsumer;
|
|
794
|
-
/**
|
|
862
|
+
/**
|
|
863
|
+
* "direct": one tool per capability — provider-native input typing, catalog
|
|
864
|
+
* size linear in the surface. "meta": three fixed tools with lazy discovery —
|
|
865
|
+
* constant tool-block size, one extra round trip before the first act.
|
|
866
|
+
* Default "direct"; see the selection guide in docs/09 §choosing-a-mode.
|
|
867
|
+
*/
|
|
795
868
|
mode?: "direct" | "meta";
|
|
796
869
|
/**
|
|
797
870
|
* Loop topology (D26). Sets the confirmation-mode default: "embedded" →
|
|
@@ -824,18 +897,50 @@ interface AgentToolsetOptions {
|
|
|
824
897
|
maxComponents?: number;
|
|
825
898
|
maxBytes?: number;
|
|
826
899
|
};
|
|
900
|
+
/**
|
|
901
|
+
* D28 compatibility flag. `true` (default, for one minor) composes
|
|
902
|
+
* availability and the contextual note into `description`, as 0.1 did.
|
|
903
|
+
* `false` keeps `description` free of live state, so the provider tool block
|
|
904
|
+
* is byte-stable across steps and prompt-prefix caching survives; the host
|
|
905
|
+
* renders `AgentTool.state` outside the tool definitions (docs/09
|
|
906
|
+
* §rendering-capability-state). `state` is populated either way.
|
|
907
|
+
*/
|
|
908
|
+
descriptionIncludesState?: boolean;
|
|
827
909
|
}
|
|
828
910
|
interface AgentTool {
|
|
829
|
-
/** Wire-safe name (docs/09 §wire-names), ≤ 64 chars. */
|
|
911
|
+
/** Wire-safe name (docs/09 §wire-names), ≤ 64 chars, unique in this catalog. */
|
|
830
912
|
name: string;
|
|
913
|
+
/**
|
|
914
|
+
* Plane + effect + confirmation prefix, then the authored description.
|
|
915
|
+
* With `descriptionIncludesState: false` this contains NO live state — it is
|
|
916
|
+
* safe in a provider tool block with prompt-prefix caching across steps.
|
|
917
|
+
*/
|
|
831
918
|
description: string;
|
|
832
919
|
inputSchema: JsonSchema;
|
|
920
|
+
/**
|
|
921
|
+
* Volatile: re-derived on every snapshot. Hosts render this OUTSIDE the tool
|
|
922
|
+
* block (e.g. a trailing system message) so availability stays honest without
|
|
923
|
+
* invalidating the cached prefix (D28).
|
|
924
|
+
*/
|
|
925
|
+
state: {
|
|
926
|
+
available: boolean;
|
|
927
|
+
unavailableReason?: string;
|
|
928
|
+
/** Live text contributed by a contextual binding's `describe()`. */
|
|
929
|
+
note?: string;
|
|
930
|
+
};
|
|
833
931
|
execute(input: JsonValue, call: {
|
|
834
932
|
toolCallId?: string;
|
|
835
933
|
}): Promise<AgentInvocationResult>;
|
|
836
934
|
}
|
|
837
935
|
interface AgentToolset {
|
|
838
936
|
tools(): AgentTool[];
|
|
937
|
+
/**
|
|
938
|
+
* wireName → canonical capability id, for the catalog `tools()` last built.
|
|
939
|
+
* Authoritative: shortened names are not decodable by string surgery, so a
|
|
940
|
+
* host MUST consult this rather than reversing names itself (D30). Empty in
|
|
941
|
+
* "meta" mode, whose three tool names are not capability ids.
|
|
942
|
+
*/
|
|
943
|
+
wireNameMap(): ReadonlyMap<string, string>;
|
|
839
944
|
/** Fires when tools() would return a different catalog. */
|
|
840
945
|
subscribe(listener: (tools: AgentTool[]) => void): Unsubscribe;
|
|
841
946
|
dispose(): void;
|
|
@@ -845,4 +950,4 @@ declare function createAgentToolset(registry: AgentSurfaceRegistry, options: Age
|
|
|
845
950
|
/** Deep equality over JsonValue (order-sensitive for arrays, docs/06 rule 2). */
|
|
846
951
|
declare function jsonDeepEqual(a: JsonValue | undefined, b: JsonValue | undefined): boolean;
|
|
847
952
|
|
|
848
|
-
export { AGENT_CAPABILITY_ERROR_CODES, type AgentActionContext, type AgentActionDefinition, type AgentActionDescriptor, type AgentAuthorizationContext, type AgentCapabilityDescriptorUnion, type AgentCapabilityErrorCode, type AgentCapabilityErrorPayload, type AgentComponentDefinition, type AgentComponentDescriptor, type AgentConcurrency, type AgentConsumer, type AgentEffect, type AgentEnvironment, type AgentErrorRetry, type AgentInvocation, type AgentInvocationPolicyContext, type AgentInvocationResult, type AgentObservationDefinition, type AgentObservationDescriptor, type AgentPlane, type AgentPolicy, type AgentPolicyContext, type AgentProcedureBinding, type AgentProcedureBindingRuntimeConfig, type AgentProcedureDescriptor, type AgentProcedureEffect, type AgentProcedureExecutor, type AgentProcedureRefDescriptor, type AgentReadContext, type AgentRegistrationHandle, type AgentRouteInfo, type AgentSchema, AgentSchemaError, type AgentSchemaIssue, AgentSurfaceDefinitionError, type AgentSurfaceDefinitionErrorCode, AgentSurfaceError, type AgentSurfaceEvent, type AgentSurfaceLimits, type AgentSurfaceRegistry, type AgentSurfaceSnapshot, type AgentTool, type AgentToolset, type AgentToolsetOptions, type AuditEvent, type AuditSink, CONFIRMATION_ESCALATION, type ConfirmationController, type ConfirmationEscalation, DEFAULT_LIMITS, type DiscoveryDecision, type InvokeOptions, type JsonSchema, type JsonValue, MAX_ID_LENGTH, MAX_WIRE_NAME_LENGTH, type ParsedCapabilityId, type PendingConfirmation, type PreconditionFailure, type ProcedureCallInfo, type RegistrationCandidate, type RegistryOptions, type SnapshotContext, type StandardSchemaV1, type Unsubscribe, action, audit, authenticated, composeInvokeChain, consoleAuditSink, createAgentSurfaceRegistry, createAgentToolset, decodeWireName, defineAgentComponent, emptyObjectSchema, encodeWireName, encodeWireNameForInstance, environment, evaluateDiscovery, formatDomainCapabilityId, formatViewCapabilityId, fromJsonSchema, fromStandardSchema, hasPermission, isAgentSurfaceError, isValidCapabilityName, isValidComponentType, isValidInstanceId, jsonDeepEqual, memoryAuditSink, observation, parseCapabilityId, rateLimit, requireConfirmation, tenantBoundary, validateComponentDefinition, validateJsonSchemaDocument, validateValueAgainstSchema };
|
|
953
|
+
export { AGENT_CAPABILITY_ERROR_CODES, type AgentActionContext, type AgentActionDefinition, type AgentActionDescriptor, type AgentAuthorizationContext, type AgentCapabilityDescriptorUnion, type AgentCapabilityErrorCode, type AgentCapabilityErrorPayload, type AgentComponentDefinition, type AgentComponentDescriptor, type AgentConcurrency, type AgentConsumer, type AgentEffect, type AgentEnvironment, type AgentErrorRetry, type AgentInvocation, type AgentInvocationPolicyContext, type AgentInvocationResult, type AgentObservationDefinition, type AgentObservationDescriptor, type AgentPlane, type AgentPolicy, type AgentPolicyContext, type AgentProcedureBinding, type AgentProcedureBindingRuntimeConfig, type AgentProcedureDescriptor, type AgentProcedureEffect, type AgentProcedureExecutor, type AgentProcedureRefDescriptor, type AgentReadContext, type AgentRegistrationHandle, type AgentRouteInfo, type AgentSchema, AgentSchemaError, type AgentSchemaIssue, AgentSurfaceDefinitionError, type AgentSurfaceDefinitionErrorCode, AgentSurfaceError, type AgentSurfaceEvent, type AgentSurfaceLimits, type AgentSurfaceRegistry, type AgentSurfaceSnapshot, type AgentTool, type AgentToolset, type AgentToolsetOptions, type AuditEvent, type AuditSink, CONFIRMATION_ESCALATION, type ConfirmationController, type ConfirmationEscalation, DEFAULT_LIMITS, type DiscoveryDecision, type InvokeOptions, type JsonSchema, type JsonValue, MAX_ID_LENGTH, MAX_WIRE_NAME_LENGTH, type ParsedCapabilityId, type PendingConfirmation, type PreconditionFailure, type ProcedureCallInfo, type RegistrationCandidate, type RegistryOptions, type SnapshotContext, type StandardSchemaV1, type Unsubscribe, type WireNameAssignment, type WireNameEntry, action, assignWireNames, audit, authenticated, composeInvokeChain, consoleAuditSink, createAgentSurfaceRegistry, createAgentToolset, decodeWireName, defineAgentComponent, emptyObjectSchema, encodeWireName, encodeWireNameForInstance, environment, evaluateDiscovery, formatDomainCapabilityId, formatViewCapabilityId, fromJsonSchema, fromStandardSchema, hasPermission, isAgentSurfaceError, isValidCapabilityName, isValidComponentType, isValidInstanceId, jsonDeepEqual, memoryAuditSink, observation, parseCapabilityId, rateLimit, requireConfirmation, stableDescriptionOf, tenantBoundary, validateComponentDefinition, validateJsonSchemaDocument, validateValueAgainstSchema };
|
package/dist/index.js
CHANGED
|
@@ -102,29 +102,81 @@ function parseCapabilityId(id) {
|
|
|
102
102
|
return void 0;
|
|
103
103
|
}
|
|
104
104
|
var MAX_WIRE_NAME_LENGTH = 64;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
var SHORTENED_MARKER = "_0_";
|
|
106
|
+
var INSTANCE_MARKER = "_at_";
|
|
107
|
+
function hash36(input, length) {
|
|
108
|
+
let out = "";
|
|
109
|
+
for (let round = 0; out.length < length; round++) {
|
|
110
|
+
let hash = (2166136261 ^ round) >>> 0;
|
|
111
|
+
for (let i = 0; i < input.length; i++) {
|
|
112
|
+
hash ^= input.charCodeAt(i);
|
|
113
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
114
|
+
}
|
|
115
|
+
out += hash.toString(36).padStart(7, "0");
|
|
110
116
|
}
|
|
111
|
-
return
|
|
117
|
+
return out.slice(0, length);
|
|
118
|
+
}
|
|
119
|
+
function rawWireName(id, instanceId) {
|
|
120
|
+
const encoded = id.replace(":", "_").replaceAll(".", "__");
|
|
121
|
+
return instanceId ? `${encoded}${INSTANCE_MARKER}${instanceId}` : encoded;
|
|
112
122
|
}
|
|
113
123
|
function encodeWireName(id) {
|
|
114
124
|
return encodeWireNameForInstance(id);
|
|
115
125
|
}
|
|
116
|
-
function encodeWireNameForInstance(id, instanceId) {
|
|
117
|
-
const raw = id
|
|
118
|
-
if (raw.length <= MAX_WIRE_NAME_LENGTH) return raw;
|
|
119
|
-
|
|
126
|
+
function encodeWireNameForInstance(id, instanceId, level = 0) {
|
|
127
|
+
const raw = rawWireName(id, instanceId);
|
|
128
|
+
if (level === 0 && raw.length <= MAX_WIRE_NAME_LENGTH) return raw;
|
|
129
|
+
const hashLength = 7 + level * 2;
|
|
130
|
+
const keep = MAX_WIRE_NAME_LENGTH - SHORTENED_MARKER.length - hashLength;
|
|
131
|
+
const hash = hash36(`${id}#${instanceId ?? ""}#${level}`, hashLength);
|
|
132
|
+
return `${raw.slice(0, keep)}${SHORTENED_MARKER}${hash}`;
|
|
133
|
+
}
|
|
134
|
+
function assignWireNames(entries) {
|
|
135
|
+
const keyOf = (e) => `${e.id}#${e.instanceId ?? ""}`;
|
|
136
|
+
const level = /* @__PURE__ */ new Map();
|
|
137
|
+
const MAX_LEVEL = 3;
|
|
138
|
+
let names = entries.map((e) => encodeWireNameForInstance(e.id, e.instanceId));
|
|
139
|
+
for (let round = 0; round <= MAX_LEVEL; round++) {
|
|
140
|
+
const byName2 = /* @__PURE__ */ new Map();
|
|
141
|
+
entries.forEach((entry, i) => {
|
|
142
|
+
const set = byName2.get(names[i]) ?? /* @__PURE__ */ new Set();
|
|
143
|
+
set.add(keyOf(entry));
|
|
144
|
+
byName2.set(names[i], set);
|
|
145
|
+
});
|
|
146
|
+
const colliding = /* @__PURE__ */ new Set();
|
|
147
|
+
for (const [, keys] of byName2) {
|
|
148
|
+
if (keys.size > 1) for (const key of keys) colliding.add(key);
|
|
149
|
+
}
|
|
150
|
+
if (colliding.size === 0) break;
|
|
151
|
+
if (round === MAX_LEVEL) {
|
|
152
|
+
const ranked = [...colliding].sort();
|
|
153
|
+
names = entries.map((entry, i) => {
|
|
154
|
+
const rank = ranked.indexOf(keyOf(entry));
|
|
155
|
+
if (rank < 0) return names[i];
|
|
156
|
+
const suffix = `${SHORTENED_MARKER}${rank}`;
|
|
157
|
+
const base = encodeWireNameForInstance(entry.id, entry.instanceId, MAX_LEVEL);
|
|
158
|
+
return `${base.slice(0, MAX_WIRE_NAME_LENGTH - suffix.length)}${suffix}`;
|
|
159
|
+
});
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
for (const key of colliding) level.set(key, (level.get(key) ?? 0) + 1);
|
|
163
|
+
names = entries.map(
|
|
164
|
+
(entry) => encodeWireNameForInstance(entry.id, entry.instanceId, level.get(keyOf(entry)) ?? 0)
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
const byName = /* @__PURE__ */ new Map();
|
|
168
|
+
entries.forEach((entry, i) => byName.set(names[i], entry.id));
|
|
169
|
+
return { names, byName };
|
|
120
170
|
}
|
|
121
171
|
function decodeWireName(name) {
|
|
172
|
+
if (name.includes(SHORTENED_MARKER) || name.includes(INSTANCE_MARKER)) return void 0;
|
|
122
173
|
const planeEnd = name.indexOf("_");
|
|
123
174
|
if (planeEnd <= 0) return void 0;
|
|
124
175
|
const plane = name.slice(0, planeEnd);
|
|
125
176
|
if (plane !== "view" && plane !== "domain") return void 0;
|
|
126
|
-
const
|
|
127
|
-
return
|
|
177
|
+
const id = `${plane}:${name.slice(planeEnd + 1).replaceAll("__", ".")}`;
|
|
178
|
+
if (id.includes("_") || !parseCapabilityId(id) || encodeWireName(id) !== name) return void 0;
|
|
179
|
+
return id;
|
|
128
180
|
}
|
|
129
181
|
|
|
130
182
|
// src/utils.ts
|
|
@@ -2520,6 +2572,12 @@ function drainObservationQueues(internals) {
|
|
|
2520
2572
|
|
|
2521
2573
|
// src/snapshot.ts
|
|
2522
2574
|
var DEFAULT_CONSUMER2 = { id: "anonymous", kind: "embedded" };
|
|
2575
|
+
function stableDescriptionOf(descriptor) {
|
|
2576
|
+
const note = descriptor.contextualNote;
|
|
2577
|
+
if (!note) return descriptor.description;
|
|
2578
|
+
if (descriptor.description === note) return "";
|
|
2579
|
+
return descriptor.description.endsWith(` ${note}`) ? descriptor.description.slice(0, -(note.length + 1)) : descriptor.description;
|
|
2580
|
+
}
|
|
2523
2581
|
function matchesScope(type, scope) {
|
|
2524
2582
|
if (!scope || scope.length === 0) return true;
|
|
2525
2583
|
return scope.some((prefix) => type === prefix || type.startsWith(`${prefix}.`));
|
|
@@ -2625,18 +2683,20 @@ function createSnapshot(internals, ctx) {
|
|
|
2625
2683
|
const available = availability.available && decision.decision === "expose";
|
|
2626
2684
|
const reason = decision.decision === "disable" ? decision.reason : availability.reason;
|
|
2627
2685
|
if (!available && !includeUnavailable) continue;
|
|
2628
|
-
let
|
|
2686
|
+
let contextualNote;
|
|
2629
2687
|
const describe = proc.binding.config.describe;
|
|
2630
2688
|
if (describe) {
|
|
2631
2689
|
try {
|
|
2632
2690
|
const contextual = describe();
|
|
2633
|
-
if (contextual)
|
|
2691
|
+
if (contextual) contextualNote = contextual;
|
|
2634
2692
|
} catch {
|
|
2635
2693
|
}
|
|
2636
2694
|
}
|
|
2695
|
+
const description = contextualNote && internals.mergesContextualNote ? `${proc.baseDescription} ${contextualNote}`.trim() : proc.baseDescription;
|
|
2637
2696
|
procedures.push({
|
|
2638
2697
|
procedureId: proc.capabilityId,
|
|
2639
2698
|
description,
|
|
2699
|
+
...contextualNote !== void 0 ? { contextualNote } : {},
|
|
2640
2700
|
inputSchema: proc.reducedInputSchema,
|
|
2641
2701
|
...proc.outputJsonSchema ? { outputSchema: proc.outputJsonSchema } : {},
|
|
2642
2702
|
effect: proc.effect,
|
|
@@ -2703,6 +2763,7 @@ function createAgentSurfaceRegistry(options) {
|
|
|
2703
2763
|
const internals = {
|
|
2704
2764
|
environment: environment2,
|
|
2705
2765
|
limits,
|
|
2766
|
+
mergesContextualNote: options?.snapshotMergesContextualNote ?? true,
|
|
2706
2767
|
surfaceId: `srf_${randomBase62(22)}`,
|
|
2707
2768
|
version: 0,
|
|
2708
2769
|
registrations: /* @__PURE__ */ new Map(),
|
|
@@ -2994,14 +3055,22 @@ var EMPTY_INPUT_SCHEMA = {
|
|
|
2994
3055
|
properties: {},
|
|
2995
3056
|
additionalProperties: false
|
|
2996
3057
|
};
|
|
2997
|
-
function describePrefix(plane, effect, confirmation
|
|
3058
|
+
function describePrefix(plane, effect, confirmation) {
|
|
2998
3059
|
const parts = [plane, effect];
|
|
2999
3060
|
if (confirmation === "required") parts.push("requires confirmation");
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
}
|
|
3004
|
-
|
|
3061
|
+
return `[${parts.join(" \xB7 ")}]`;
|
|
3062
|
+
}
|
|
3063
|
+
function legacyDescription(prefix, description, state) {
|
|
3064
|
+
const unavailable = state.available ? "" : ` [currently unavailable${state.unavailableReason ? `: ${state.unavailableReason}` : ""}]`;
|
|
3065
|
+
const note = state.note ? ` ${state.note}` : "";
|
|
3066
|
+
return `${prefix}${unavailable} ${description}${note}`;
|
|
3067
|
+
}
|
|
3068
|
+
function availabilityState(descriptor) {
|
|
3069
|
+
return {
|
|
3070
|
+
available: descriptor.available,
|
|
3071
|
+
...descriptor.unavailableReason !== void 0 ? { unavailableReason: descriptor.unavailableReason } : {},
|
|
3072
|
+
...descriptor.contextualNote !== void 0 ? { note: descriptor.contextualNote } : {}
|
|
3073
|
+
};
|
|
3005
3074
|
}
|
|
3006
3075
|
function createAgentToolset(registry, options) {
|
|
3007
3076
|
const mode = options.mode ?? "direct";
|
|
@@ -3016,11 +3085,13 @@ function createAgentToolset(registry, options) {
|
|
|
3016
3085
|
);
|
|
3017
3086
|
}
|
|
3018
3087
|
const confirmationsMode = options.confirmations ?? (options.topology === "remote" ? "two-phase" : "wait");
|
|
3088
|
+
const descriptionIncludesState = options.descriptionIncludesState ?? true;
|
|
3019
3089
|
const listeners = /* @__PURE__ */ new Set();
|
|
3020
3090
|
const pendingWaits = /* @__PURE__ */ new Set();
|
|
3021
3091
|
let disposed = false;
|
|
3022
3092
|
let cachedVersion;
|
|
3023
3093
|
let cachedTools;
|
|
3094
|
+
let cachedWireNames = /* @__PURE__ */ new Map();
|
|
3024
3095
|
let cachedSignature;
|
|
3025
3096
|
async function waitForConfirmation(confirmationId) {
|
|
3026
3097
|
if (disposed) return;
|
|
@@ -3063,26 +3134,32 @@ function createAgentToolset(registry, options) {
|
|
|
3063
3134
|
...options.scope ? { scope: options.scope } : {},
|
|
3064
3135
|
includeUnavailable: true
|
|
3065
3136
|
});
|
|
3066
|
-
const
|
|
3067
|
-
const push = (capabilityId, kind, registrationId, instanceId, description, inputSchema, nameSuffix) => {
|
|
3068
|
-
const
|
|
3069
|
-
|
|
3070
|
-
registrationId,
|
|
3071
|
-
...instanceId !== void 0 ? { instanceId } : {},
|
|
3072
|
-
surfaceVersion: snapshot.surfaceVersion,
|
|
3073
|
-
kind
|
|
3074
|
-
};
|
|
3075
|
-
tools.push({
|
|
3137
|
+
const pending = [];
|
|
3138
|
+
const push = (capabilityId, kind, registrationId, instanceId, prefix, description, inputSchema, state, nameSuffix) => {
|
|
3139
|
+
const suffix = nameSuffix ?? instanceId;
|
|
3140
|
+
pending.push({
|
|
3076
3141
|
// Providers require unique tool names: multi-instance capabilities
|
|
3077
3142
|
// are disambiguated with an `_at_<instance>` suffix (docs/09).
|
|
3078
|
-
|
|
3143
|
+
wire: { id: capabilityId, ...suffix !== void 0 ? { instanceId: suffix } : {} },
|
|
3144
|
+
entry: {
|
|
3145
|
+
capabilityId,
|
|
3146
|
+
registrationId,
|
|
3147
|
+
...instanceId !== void 0 ? { instanceId } : {},
|
|
3148
|
+
surfaceVersion: snapshot.surfaceVersion,
|
|
3149
|
+
kind
|
|
3150
|
+
},
|
|
3151
|
+
prefix,
|
|
3079
3152
|
description,
|
|
3080
3153
|
inputSchema,
|
|
3081
|
-
|
|
3154
|
+
state
|
|
3082
3155
|
});
|
|
3083
3156
|
};
|
|
3157
|
+
const typeCounts = /* @__PURE__ */ new Map();
|
|
3158
|
+
for (const component of snapshot.components) {
|
|
3159
|
+
typeCounts.set(component.type, (typeCounts.get(component.type) ?? 0) + 1);
|
|
3160
|
+
}
|
|
3084
3161
|
for (const component of snapshot.components) {
|
|
3085
|
-
const multiInstance =
|
|
3162
|
+
const multiInstance = (typeCounts.get(component.type) ?? 0) > 1;
|
|
3086
3163
|
const instanceId = multiInstance ? component.instanceId : void 0;
|
|
3087
3164
|
for (const obs of component.observations) {
|
|
3088
3165
|
push(
|
|
@@ -3090,8 +3167,10 @@ function createAgentToolset(registry, options) {
|
|
|
3090
3167
|
"observation",
|
|
3091
3168
|
component.registrationId,
|
|
3092
3169
|
instanceId,
|
|
3093
|
-
|
|
3094
|
-
|
|
3170
|
+
describePrefix("view", "read", "never"),
|
|
3171
|
+
obs.description,
|
|
3172
|
+
EMPTY_INPUT_SCHEMA,
|
|
3173
|
+
availabilityState(obs)
|
|
3095
3174
|
);
|
|
3096
3175
|
}
|
|
3097
3176
|
for (const act of component.actions) {
|
|
@@ -3100,8 +3179,10 @@ function createAgentToolset(registry, options) {
|
|
|
3100
3179
|
"action",
|
|
3101
3180
|
component.registrationId,
|
|
3102
3181
|
instanceId,
|
|
3103
|
-
|
|
3104
|
-
act.
|
|
3182
|
+
describePrefix("view", act.effect, act.confirmation),
|
|
3183
|
+
act.description,
|
|
3184
|
+
act.inputSchema,
|
|
3185
|
+
availabilityState(act)
|
|
3105
3186
|
);
|
|
3106
3187
|
}
|
|
3107
3188
|
}
|
|
@@ -3116,25 +3197,45 @@ function createAgentToolset(registry, options) {
|
|
|
3116
3197
|
"procedure",
|
|
3117
3198
|
proc.registrationId,
|
|
3118
3199
|
void 0,
|
|
3119
|
-
|
|
3200
|
+
describePrefix("domain", proc.effect, proc.confirmation),
|
|
3201
|
+
// The stable half only: a contextual note travels in `state.note`.
|
|
3202
|
+
stableDescriptionOf(proc),
|
|
3120
3203
|
proc.inputSchema,
|
|
3204
|
+
availabilityState(proc),
|
|
3121
3205
|
needsSuffix ? proc.context?.instanceId ?? proc.registrationId.replace(/[^A-Za-z0-9_-]/g, "") : void 0
|
|
3122
3206
|
);
|
|
3123
3207
|
}
|
|
3124
|
-
|
|
3208
|
+
const assignment = assignWireNames(pending.map((p) => p.wire));
|
|
3209
|
+
const tools = pending.map((p, i) => ({
|
|
3210
|
+
name: assignment.names[i],
|
|
3211
|
+
description: descriptionIncludesState ? legacyDescription(p.prefix, p.description, p.state) : `${p.prefix} ${p.description}`,
|
|
3212
|
+
inputSchema: p.inputSchema,
|
|
3213
|
+
state: p.state,
|
|
3214
|
+
execute: (input, call) => invokeThroughSurface(p.entry, input, call.toolCallId)
|
|
3215
|
+
}));
|
|
3216
|
+
return { tools, wireNames: assignment.byName };
|
|
3125
3217
|
}
|
|
3126
3218
|
function buildMetaTools() {
|
|
3127
3219
|
const snapshotFor = () => registry.snapshot({
|
|
3128
3220
|
consumer: options.consumer,
|
|
3129
3221
|
...options.scope ? { scope: options.scope } : {}
|
|
3130
3222
|
});
|
|
3131
|
-
|
|
3223
|
+
const verbs = [
|
|
3132
3224
|
{
|
|
3133
3225
|
name: "surface_discover",
|
|
3134
3226
|
description: "[meta] Discover the current agent surface: components, capabilities, procedures, availability, schemas.",
|
|
3135
3227
|
inputSchema: {
|
|
3136
3228
|
type: "object",
|
|
3137
|
-
properties: {
|
|
3229
|
+
properties: {
|
|
3230
|
+
scope: {
|
|
3231
|
+
type: "array",
|
|
3232
|
+
items: { type: "string" },
|
|
3233
|
+
// No enum: valid tokens are live component types, and inlining
|
|
3234
|
+
// them would make this tool block churn on every mount —
|
|
3235
|
+
// the churn AS-META-005 and D28 exist to prevent.
|
|
3236
|
+
description: 'Component-type prefixes to narrow the result, e.g. ["devices.table"], taken from `components[].type` of an earlier call \u2014 omit on the first. Narrows only: prefixes outside this host\'s configured scope match nothing and come back in `scopeRejected`.'
|
|
3237
|
+
}
|
|
3238
|
+
},
|
|
3138
3239
|
additionalProperties: false
|
|
3139
3240
|
},
|
|
3140
3241
|
async execute(input) {
|
|
@@ -3145,7 +3246,14 @@ function createAgentToolset(registry, options) {
|
|
|
3145
3246
|
...effective.scope ? { scope: effective.scope } : {},
|
|
3146
3247
|
...options.budget ? { budget: options.budget } : {}
|
|
3147
3248
|
});
|
|
3148
|
-
const projected =
|
|
3249
|
+
const projected = {
|
|
3250
|
+
...snapshot,
|
|
3251
|
+
// A disjoint request is snapshotted unscoped, so any `truncated`
|
|
3252
|
+
// count belongs to a surface this payload does not contain. Keeping
|
|
3253
|
+
// it would claim a budget dropped what scope did.
|
|
3254
|
+
...effective.empty ? { components: [], procedures: [], truncated: void 0 } : {},
|
|
3255
|
+
...effective.rejected.length > 0 ? { scopeRejected: { prefixes: effective.rejected } } : {}
|
|
3256
|
+
};
|
|
3149
3257
|
return {
|
|
3150
3258
|
status: "ok",
|
|
3151
3259
|
invocationId: `inv_${randomBase62(12)}`,
|
|
@@ -3161,8 +3269,14 @@ function createAgentToolset(registry, options) {
|
|
|
3161
3269
|
inputSchema: {
|
|
3162
3270
|
type: "object",
|
|
3163
3271
|
properties: {
|
|
3164
|
-
capabilityId: {
|
|
3165
|
-
|
|
3272
|
+
capabilityId: {
|
|
3273
|
+
type: "string",
|
|
3274
|
+
description: "Observation id, verbatim from `observations[].capabilityId` in a discover result."
|
|
3275
|
+
},
|
|
3276
|
+
instanceId: {
|
|
3277
|
+
type: "string",
|
|
3278
|
+
description: "Only when several components share a type: `components[].instanceId` picks one."
|
|
3279
|
+
}
|
|
3166
3280
|
},
|
|
3167
3281
|
required: ["capabilityId"],
|
|
3168
3282
|
additionalProperties: false
|
|
@@ -3187,15 +3301,31 @@ function createAgentToolset(registry, options) {
|
|
|
3187
3301
|
},
|
|
3188
3302
|
{
|
|
3189
3303
|
name: "surface_act",
|
|
3190
|
-
description: "[meta] Invoke an action or procedure by capabilityId.",
|
|
3304
|
+
description: "[meta] Invoke an action or procedure by capabilityId. Echo the surfaceVersion you discovered so a surface that changed underneath a destructive plan is rejected rather than executed.",
|
|
3191
3305
|
inputSchema: {
|
|
3192
3306
|
type: "object",
|
|
3193
3307
|
properties: {
|
|
3194
|
-
capabilityId: {
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3308
|
+
capabilityId: {
|
|
3309
|
+
type: "string",
|
|
3310
|
+
description: "Action `capabilityId` or `procedureId`, verbatim from a discover result."
|
|
3311
|
+
},
|
|
3312
|
+
instanceId: {
|
|
3313
|
+
type: "string",
|
|
3314
|
+
description: "Only when several components share a type: `components[].instanceId` picks one."
|
|
3315
|
+
},
|
|
3316
|
+
input: { description: "Arguments matching that capability's `inputSchema`." },
|
|
3317
|
+
invocationId: {
|
|
3318
|
+
type: "string",
|
|
3319
|
+
description: "Reuse a previous call's id to retry without executing twice; required when resuming after CONFIRMATION_REQUIRED."
|
|
3320
|
+
},
|
|
3321
|
+
confirmationId: {
|
|
3322
|
+
type: "string",
|
|
3323
|
+
description: "The id returned with CONFIRMATION_REQUIRED, sent back after the user approves."
|
|
3324
|
+
},
|
|
3325
|
+
surfaceVersion: {
|
|
3326
|
+
type: "string",
|
|
3327
|
+
description: "The `surfaceVersion` you planned against. Send it for destructive or externally-visible calls: a surface that moved underneath the plan then fails instead of executing. Omitted, the call binds to what is live now."
|
|
3328
|
+
}
|
|
3199
3329
|
},
|
|
3200
3330
|
required: ["capabilityId"],
|
|
3201
3331
|
additionalProperties: false
|
|
@@ -3209,7 +3339,7 @@ function createAgentToolset(registry, options) {
|
|
|
3209
3339
|
capabilityId: req.capabilityId,
|
|
3210
3340
|
...registrationId !== void 0 ? { registrationId } : {},
|
|
3211
3341
|
...req.instanceId !== void 0 ? { instanceId: req.instanceId } : {},
|
|
3212
|
-
surfaceVersion: snapshot.surfaceVersion,
|
|
3342
|
+
surfaceVersion: req.surfaceVersion ?? snapshot.surfaceVersion,
|
|
3213
3343
|
kind: "action"
|
|
3214
3344
|
},
|
|
3215
3345
|
req.input,
|
|
@@ -3222,6 +3352,7 @@ function createAgentToolset(registry, options) {
|
|
|
3222
3352
|
}
|
|
3223
3353
|
}
|
|
3224
3354
|
];
|
|
3355
|
+
return verbs.map((verb) => ({ ...verb, state: { available: true } }));
|
|
3225
3356
|
}
|
|
3226
3357
|
function computeTools() {
|
|
3227
3358
|
if (mode === "meta") {
|
|
@@ -3230,13 +3361,15 @@ function createAgentToolset(registry, options) {
|
|
|
3230
3361
|
}
|
|
3231
3362
|
const version = registry.getVersion();
|
|
3232
3363
|
if (cachedTools && cachedVersion === version) return cachedTools;
|
|
3233
|
-
|
|
3364
|
+
const built = buildDirectTools();
|
|
3365
|
+
cachedTools = built.tools;
|
|
3366
|
+
cachedWireNames = built.wireNames;
|
|
3234
3367
|
cachedVersion = version;
|
|
3235
3368
|
return cachedTools;
|
|
3236
3369
|
}
|
|
3237
3370
|
function signatureOf(tools) {
|
|
3238
3371
|
return JSON.stringify(
|
|
3239
|
-
tools.map((t) => [t.name, t.description, t.inputSchema])
|
|
3372
|
+
tools.map((t) => [t.name, t.description, t.inputSchema, t.state])
|
|
3240
3373
|
);
|
|
3241
3374
|
}
|
|
3242
3375
|
const unsubscribe = registry.subscribe((event) => {
|
|
@@ -3260,6 +3393,11 @@ function createAgentToolset(registry, options) {
|
|
|
3260
3393
|
cachedSignature ??= signatureOf(tools);
|
|
3261
3394
|
return tools;
|
|
3262
3395
|
},
|
|
3396
|
+
wireNameMap() {
|
|
3397
|
+
if (mode === "meta") return /* @__PURE__ */ new Map();
|
|
3398
|
+
computeTools();
|
|
3399
|
+
return cachedWireNames;
|
|
3400
|
+
},
|
|
3263
3401
|
subscribe(listener) {
|
|
3264
3402
|
listeners.add(listener);
|
|
3265
3403
|
return () => {
|
|
@@ -3278,17 +3416,25 @@ function createAgentToolset(registry, options) {
|
|
|
3278
3416
|
function intersectScope(floor, requested) {
|
|
3279
3417
|
const hasFloor = floor !== void 0 && floor.length > 0;
|
|
3280
3418
|
if (requested === void 0 || requested.length === 0) {
|
|
3281
|
-
return hasFloor ? { scope: floor, empty: false } : { empty: false };
|
|
3419
|
+
return hasFloor ? { scope: floor, empty: false, rejected: [] } : { empty: false, rejected: [] };
|
|
3282
3420
|
}
|
|
3283
|
-
if (!hasFloor) return { scope: requested, empty: false };
|
|
3421
|
+
if (!hasFloor) return { scope: requested, empty: false, rejected: [] };
|
|
3284
3422
|
const out = /* @__PURE__ */ new Set();
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3423
|
+
const rejected = [];
|
|
3424
|
+
for (const r of requested) {
|
|
3425
|
+
let admitted = false;
|
|
3426
|
+
for (const f of floor) {
|
|
3427
|
+
if (r === f || r.startsWith(`${f}.`)) {
|
|
3428
|
+
out.add(r);
|
|
3429
|
+
admitted = true;
|
|
3430
|
+
} else if (f.startsWith(`${r}.`)) {
|
|
3431
|
+
out.add(f);
|
|
3432
|
+
admitted = true;
|
|
3433
|
+
}
|
|
3289
3434
|
}
|
|
3435
|
+
if (!admitted && !rejected.includes(r)) rejected.push(r);
|
|
3290
3436
|
}
|
|
3291
|
-
return out.size > 0 ? { scope: [...out], empty: false } : { empty: true };
|
|
3437
|
+
return out.size > 0 ? { scope: [...out], empty: false, rejected } : { empty: true, rejected };
|
|
3292
3438
|
}
|
|
3293
3439
|
function findRegistrationId(snapshot, capabilityId, instanceId) {
|
|
3294
3440
|
const matches = [];
|
|
@@ -3315,6 +3461,7 @@ export {
|
|
|
3315
3461
|
MAX_ID_LENGTH,
|
|
3316
3462
|
MAX_WIRE_NAME_LENGTH,
|
|
3317
3463
|
action,
|
|
3464
|
+
assignWireNames,
|
|
3318
3465
|
audit,
|
|
3319
3466
|
authenticated,
|
|
3320
3467
|
composeInvokeChain,
|
|
@@ -3343,6 +3490,7 @@ export {
|
|
|
3343
3490
|
parseCapabilityId,
|
|
3344
3491
|
rateLimit,
|
|
3345
3492
|
requireConfirmation,
|
|
3493
|
+
stableDescriptionOf,
|
|
3346
3494
|
tenantBoundary,
|
|
3347
3495
|
validateComponentDefinition,
|
|
3348
3496
|
validateJsonSchemaDocument,
|