@agent-surface/core 0.4.1 → 0.6.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/dist/index.d.ts +13 -39
- package/dist/index.js +212 -89
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -171,6 +171,14 @@ declare function assignWireNames(entries: readonly WireNameEntry[]): WireNameAss
|
|
|
171
171
|
* names among them (`AS-WIRE-007`: consult `toolset.wireNameMap()` instead).
|
|
172
172
|
* Returning a plausible-but-wrong canonical id would take the audit identity
|
|
173
173
|
* with it, so this refuses anything it cannot re-encode byte-identically.
|
|
174
|
+
*
|
|
175
|
+
* Refusal is decided by what a name *is*, never by a substring it happens to
|
|
176
|
+
* contain. `view:at.a.a` encodes to `view_at__a__a`, where `_at_` is the plane
|
|
177
|
+
* separator meeting a segment named "at" — screening for the marker text cost
|
|
178
|
+
* every id with an `at` or `0` segment its own faithful encoding (`AS-ID-004`).
|
|
179
|
+
* Marker-bearing names are still refused, by the two checks that can tell:
|
|
180
|
+
* every underscore run must be exactly two (one "."), and the id must re-encode
|
|
181
|
+
* byte-identically.
|
|
174
182
|
*/
|
|
175
183
|
declare function decodeWireName(name: string): string | undefined;
|
|
176
184
|
|
|
@@ -749,17 +757,10 @@ interface AgentProcedureDescriptor {
|
|
|
749
757
|
procedureId: string;
|
|
750
758
|
/**
|
|
751
759
|
* The manifest description. Stable across snapshots — the contextual
|
|
752
|
-
* `describe()` output is `contextualNote
|
|
753
|
-
* the registry was created with `snapshotMergesContextualNote: true`
|
|
754
|
-
* (the 0.2 default, removed in a later minor — D28).
|
|
760
|
+
* `describe()` output is `contextualNote` and is never folded in here (D28).
|
|
755
761
|
*/
|
|
756
762
|
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
|
+
/** Volatile: this snapshot's contextual `describe()` output, if any. */
|
|
763
764
|
contextualNote?: string;
|
|
764
765
|
/** Agent-facing (reduced) input schema per binding rule 1 (docs/05). */
|
|
765
766
|
inputSchema: JsonSchema;
|
|
@@ -783,15 +784,6 @@ interface AgentProcedureDescriptor {
|
|
|
783
784
|
meta?: Record<string, JsonValue>;
|
|
784
785
|
}
|
|
785
786
|
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;
|
|
795
787
|
|
|
796
788
|
interface RegistrationCandidate {
|
|
797
789
|
definition: AgentComponentDefinition;
|
|
@@ -815,15 +807,6 @@ interface RegistryOptions {
|
|
|
815
807
|
/** Route descriptor for snapshots (host wires its router here). */
|
|
816
808
|
route?: () => AgentRouteInfo | undefined;
|
|
817
809
|
limits?: Partial<AgentSurfaceLimits>;
|
|
818
|
-
/**
|
|
819
|
-
* D28 compatibility flag. `true` (default through 0.4; flips in 0.5) 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;
|
|
827
810
|
/** Injectable clock (docs/08 determinism); default Date.now. */
|
|
828
811
|
now?: () => number;
|
|
829
812
|
}
|
|
@@ -897,23 +880,14 @@ interface AgentToolsetOptions {
|
|
|
897
880
|
maxComponents?: number;
|
|
898
881
|
maxBytes?: number;
|
|
899
882
|
};
|
|
900
|
-
/**
|
|
901
|
-
* D28 compatibility flag. `true` (default through 0.4; flips in 0.5) 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;
|
|
909
883
|
}
|
|
910
884
|
interface AgentTool {
|
|
911
885
|
/** Wire-safe name (docs/09 §wire-names), ≤ 64 chars, unique in this catalog. */
|
|
912
886
|
name: string;
|
|
913
887
|
/**
|
|
914
888
|
* Plane + effect + confirmation prefix, then the authored description.
|
|
915
|
-
*
|
|
916
|
-
*
|
|
889
|
+
* Contains NO live state (D28), so it is safe in a provider tool block with
|
|
890
|
+
* prompt-prefix caching across steps.
|
|
917
891
|
*/
|
|
918
892
|
description: string;
|
|
919
893
|
inputSchema: JsonSchema;
|
|
@@ -950,4 +924,4 @@ declare function createAgentToolset(registry: AgentSurfaceRegistry, options: Age
|
|
|
950
924
|
/** Deep equality over JsonValue (order-sensitive for arrays, docs/06 rule 2). */
|
|
951
925
|
declare function jsonDeepEqual(a: JsonValue | undefined, b: JsonValue | undefined): boolean;
|
|
952
926
|
|
|
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,
|
|
927
|
+
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, tenantBoundary, validateComponentDefinition, validateJsonSchemaDocument, validateValueAgainstSchema };
|
package/dist/index.js
CHANGED
|
@@ -82,7 +82,7 @@ function formatDomainCapabilityId(path) {
|
|
|
82
82
|
return `domain:${path}`;
|
|
83
83
|
}
|
|
84
84
|
function parseCapabilityId(id) {
|
|
85
|
-
if (id.length > MAX_ID_LENGTH) return void 0;
|
|
85
|
+
if (typeof id !== "string" || id.length > MAX_ID_LENGTH) return void 0;
|
|
86
86
|
if (id.startsWith("view:")) {
|
|
87
87
|
const rest = id.slice("view:".length);
|
|
88
88
|
const lastDot = rest.lastIndexOf(".");
|
|
@@ -125,7 +125,7 @@ function encodeWireName(id) {
|
|
|
125
125
|
}
|
|
126
126
|
function encodeWireNameForInstance(id, instanceId, level = 0) {
|
|
127
127
|
const raw = rawWireName(id, instanceId);
|
|
128
|
-
if (level === 0 && raw.length <= MAX_WIRE_NAME_LENGTH) return raw;
|
|
128
|
+
if (level === 0 && raw.length <= MAX_WIRE_NAME_LENGTH && !id.includes("_")) return raw;
|
|
129
129
|
const hashLength = 7 + level * 2;
|
|
130
130
|
const keep = MAX_WIRE_NAME_LENGTH - SHORTENED_MARKER.length - hashLength;
|
|
131
131
|
const hash = hash36(`${id}#${instanceId ?? ""}#${level}`, hashLength);
|
|
@@ -169,12 +169,15 @@ function assignWireNames(entries) {
|
|
|
169
169
|
return { names, byName };
|
|
170
170
|
}
|
|
171
171
|
function decodeWireName(name) {
|
|
172
|
-
if (name.includes(SHORTENED_MARKER) || name.includes(INSTANCE_MARKER)) return void 0;
|
|
173
172
|
const planeEnd = name.indexOf("_");
|
|
174
173
|
if (planeEnd <= 0) return void 0;
|
|
175
174
|
const plane = name.slice(0, planeEnd);
|
|
176
175
|
if (plane !== "view" && plane !== "domain") return void 0;
|
|
177
|
-
const
|
|
176
|
+
const rest = name.slice(planeEnd + 1);
|
|
177
|
+
if (/_{3,}/.test(rest)) return void 0;
|
|
178
|
+
const path = rest.replaceAll("__", ".");
|
|
179
|
+
if (path.split(".").some((segment) => segment === "")) return void 0;
|
|
180
|
+
const id = `${plane}:${path}`;
|
|
178
181
|
if (id.includes("_") || !parseCapabilityId(id) || encodeWireName(id) !== name) return void 0;
|
|
179
182
|
return id;
|
|
180
183
|
}
|
|
@@ -1361,6 +1364,7 @@ var ConfirmationStore = class {
|
|
|
1361
1364
|
};
|
|
1362
1365
|
|
|
1363
1366
|
// src/internal.ts
|
|
1367
|
+
var DEV_WARN = /* @__PURE__ */ Symbol("agent-surface.dev-warn");
|
|
1364
1368
|
var DevDefectError = class extends Error {
|
|
1365
1369
|
constructor(message) {
|
|
1366
1370
|
super(message);
|
|
@@ -2572,12 +2576,6 @@ function drainObservationQueues(internals) {
|
|
|
2572
2576
|
|
|
2573
2577
|
// src/snapshot.ts
|
|
2574
2578
|
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
|
-
}
|
|
2581
2579
|
function matchesScope(type, scope) {
|
|
2582
2580
|
if (!scope || scope.length === 0) return true;
|
|
2583
2581
|
return scope.some((prefix) => type === prefix || type.startsWith(`${prefix}.`));
|
|
@@ -2692,10 +2690,12 @@ function createSnapshot(internals, ctx) {
|
|
|
2692
2690
|
} catch {
|
|
2693
2691
|
}
|
|
2694
2692
|
}
|
|
2695
|
-
const description = contextualNote && internals.mergesContextualNote ? `${proc.baseDescription} ${contextualNote}`.trim() : proc.baseDescription;
|
|
2696
2693
|
procedures.push({
|
|
2697
2694
|
procedureId: proc.capabilityId,
|
|
2698
|
-
|
|
2695
|
+
// Never merged with `contextualNote` (D28): the manifest text is the
|
|
2696
|
+
// stable half, and folding volatile text in is what churned the
|
|
2697
|
+
// provider's cached prompt prefix.
|
|
2698
|
+
description: proc.baseDescription,
|
|
2699
2699
|
...contextualNote !== void 0 ? { contextualNote } : {},
|
|
2700
2700
|
inputSchema: proc.reducedInputSchema,
|
|
2701
2701
|
...proc.outputJsonSchema ? { outputSchema: proc.outputJsonSchema } : {},
|
|
@@ -2763,7 +2763,6 @@ function createAgentSurfaceRegistry(options) {
|
|
|
2763
2763
|
const internals = {
|
|
2764
2764
|
environment: environment2,
|
|
2765
2765
|
limits,
|
|
2766
|
-
mergesContextualNote: options?.snapshotMergesContextualNote ?? true,
|
|
2767
2766
|
surfaceId: `srf_${randomBase62(22)}`,
|
|
2768
2767
|
version: 0,
|
|
2769
2768
|
registrations: /* @__PURE__ */ new Map(),
|
|
@@ -3039,6 +3038,10 @@ function createAgentSurfaceRegistry(options) {
|
|
|
3039
3038
|
dispatcher.clear();
|
|
3040
3039
|
}
|
|
3041
3040
|
};
|
|
3041
|
+
Object.defineProperty(registry, DEV_WARN, {
|
|
3042
|
+
value: (...args) => internals.devWarn(...args),
|
|
3043
|
+
enumerable: false
|
|
3044
|
+
});
|
|
3042
3045
|
return registry;
|
|
3043
3046
|
}
|
|
3044
3047
|
function combineSinks(...sinks) {
|
|
@@ -3055,16 +3058,80 @@ var EMPTY_INPUT_SCHEMA = {
|
|
|
3055
3058
|
properties: {},
|
|
3056
3059
|
additionalProperties: false
|
|
3057
3060
|
};
|
|
3061
|
+
var META_DISCOVER_SCHEMA = {
|
|
3062
|
+
type: "object",
|
|
3063
|
+
properties: {
|
|
3064
|
+
scope: {
|
|
3065
|
+
type: "array",
|
|
3066
|
+
items: { type: "string" },
|
|
3067
|
+
// No enum: valid tokens are live component types, and inlining them
|
|
3068
|
+
// would make this tool block churn on every mount — the churn
|
|
3069
|
+
// AS-META-005 and D28 exist to prevent.
|
|
3070
|
+
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`.'
|
|
3071
|
+
}
|
|
3072
|
+
},
|
|
3073
|
+
additionalProperties: false
|
|
3074
|
+
};
|
|
3075
|
+
var META_READ_SCHEMA = {
|
|
3076
|
+
type: "object",
|
|
3077
|
+
properties: {
|
|
3078
|
+
capabilityId: {
|
|
3079
|
+
type: "string",
|
|
3080
|
+
description: "Observation id, verbatim from `observations[].capabilityId` in a discover result."
|
|
3081
|
+
},
|
|
3082
|
+
instanceId: {
|
|
3083
|
+
type: "string",
|
|
3084
|
+
description: "Only when several components share a type: `components[].instanceId` picks one."
|
|
3085
|
+
}
|
|
3086
|
+
},
|
|
3087
|
+
required: ["capabilityId"],
|
|
3088
|
+
additionalProperties: false
|
|
3089
|
+
};
|
|
3090
|
+
var META_ACT_SCHEMA = {
|
|
3091
|
+
type: "object",
|
|
3092
|
+
properties: {
|
|
3093
|
+
capabilityId: {
|
|
3094
|
+
type: "string",
|
|
3095
|
+
description: "Action `capabilityId` or `procedureId`, verbatim from a discover result."
|
|
3096
|
+
},
|
|
3097
|
+
instanceId: {
|
|
3098
|
+
type: "string",
|
|
3099
|
+
description: "Only when several components share a type: `components[].instanceId` picks one."
|
|
3100
|
+
},
|
|
3101
|
+
// Typed, and not merely described: an untyped property is the one position
|
|
3102
|
+
// a provider's constrained decoder cannot constrain, so the model falls
|
|
3103
|
+
// back to its prior — a JSON-encoded string, the shape
|
|
3104
|
+
// `function_call.arguments` carries — and sorts the rest of the capability's
|
|
3105
|
+
// arguments into the sibling modifiers below. `type: "object"` costs
|
|
3106
|
+
// nothing in practice: direct mode already passes `act.inputSchema`
|
|
3107
|
+
// straight through as the tool schema, and providers require that to be an
|
|
3108
|
+
// object schema at the top level. No `additionalProperties` here — the
|
|
3109
|
+
// capability's own schema governs what goes inside.
|
|
3110
|
+
input: {
|
|
3111
|
+
type: "object",
|
|
3112
|
+
description: "Arguments matching that capability's `inputSchema`, as a JSON object \u2014 not a JSON-encoded string. Everything the capability declares goes in here, never beside it."
|
|
3113
|
+
},
|
|
3114
|
+
invocationId: {
|
|
3115
|
+
type: "string",
|
|
3116
|
+
description: "Reuse a previous call's id to retry without executing twice; required when resuming after CONFIRMATION_REQUIRED."
|
|
3117
|
+
},
|
|
3118
|
+
confirmationId: {
|
|
3119
|
+
type: "string",
|
|
3120
|
+
description: "The id returned with CONFIRMATION_REQUIRED, sent back after the user approves."
|
|
3121
|
+
},
|
|
3122
|
+
surfaceVersion: {
|
|
3123
|
+
type: "string",
|
|
3124
|
+
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."
|
|
3125
|
+
}
|
|
3126
|
+
},
|
|
3127
|
+
required: ["capabilityId"],
|
|
3128
|
+
additionalProperties: false
|
|
3129
|
+
};
|
|
3058
3130
|
function describePrefix(plane, effect, confirmation) {
|
|
3059
3131
|
const parts = [plane, effect];
|
|
3060
3132
|
if (confirmation === "required") parts.push("requires confirmation");
|
|
3061
3133
|
return `[${parts.join(" \xB7 ")}]`;
|
|
3062
3134
|
}
|
|
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
3135
|
function availabilityState(descriptor) {
|
|
3069
3136
|
return {
|
|
3070
3137
|
available: descriptor.available,
|
|
@@ -3085,7 +3152,8 @@ function createAgentToolset(registry, options) {
|
|
|
3085
3152
|
);
|
|
3086
3153
|
}
|
|
3087
3154
|
const confirmationsMode = options.confirmations ?? (options.topology === "remote" ? "two-phase" : "wait");
|
|
3088
|
-
const
|
|
3155
|
+
const devWarn = registry[DEV_WARN] ?? (() => {
|
|
3156
|
+
});
|
|
3089
3157
|
const listeners = /* @__PURE__ */ new Set();
|
|
3090
3158
|
const pendingWaits = /* @__PURE__ */ new Set();
|
|
3091
3159
|
let disposed = false;
|
|
@@ -3199,7 +3267,7 @@ function createAgentToolset(registry, options) {
|
|
|
3199
3267
|
void 0,
|
|
3200
3268
|
describePrefix("domain", proc.effect, proc.confirmation),
|
|
3201
3269
|
// The stable half only: a contextual note travels in `state.note`.
|
|
3202
|
-
|
|
3270
|
+
proc.description,
|
|
3203
3271
|
proc.inputSchema,
|
|
3204
3272
|
availabilityState(proc),
|
|
3205
3273
|
needsSuffix ? proc.context?.instanceId ?? proc.registrationId.replace(/[^A-Za-z0-9_-]/g, "") : void 0
|
|
@@ -3208,13 +3276,22 @@ function createAgentToolset(registry, options) {
|
|
|
3208
3276
|
const assignment = assignWireNames(pending.map((p) => p.wire));
|
|
3209
3277
|
const tools = pending.map((p, i) => ({
|
|
3210
3278
|
name: assignment.names[i],
|
|
3211
|
-
description:
|
|
3279
|
+
description: `${p.prefix} ${p.description}`,
|
|
3212
3280
|
inputSchema: p.inputSchema,
|
|
3213
3281
|
state: p.state,
|
|
3214
3282
|
execute: (input, call) => invokeThroughSurface(p.entry, input, call.toolCallId)
|
|
3215
3283
|
}));
|
|
3216
3284
|
return { tools, wireNames: assignment.byName };
|
|
3217
3285
|
}
|
|
3286
|
+
function envelopeFailure(metaCapabilityId, capabilityId, error, toolCallId) {
|
|
3287
|
+
return {
|
|
3288
|
+
status: "error",
|
|
3289
|
+
invocationId: toolCallId ?? `inv_${randomBase62(12)}`,
|
|
3290
|
+
capabilityId: typeof capabilityId === "string" && capabilityId.length > 0 ? capabilityId : metaCapabilityId,
|
|
3291
|
+
error,
|
|
3292
|
+
surfaceVersion: registry.getVersion()
|
|
3293
|
+
};
|
|
3294
|
+
}
|
|
3218
3295
|
function buildMetaTools() {
|
|
3219
3296
|
const snapshotFor = () => registry.snapshot({
|
|
3220
3297
|
consumer: options.consumer,
|
|
@@ -3224,21 +3301,12 @@ function createAgentToolset(registry, options) {
|
|
|
3224
3301
|
{
|
|
3225
3302
|
name: "surface_discover",
|
|
3226
3303
|
description: "[meta] Discover the current agent surface: components, capabilities, procedures, availability, schemas.",
|
|
3227
|
-
inputSchema:
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
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
|
-
},
|
|
3239
|
-
additionalProperties: false
|
|
3240
|
-
},
|
|
3241
|
-
async execute(input) {
|
|
3304
|
+
inputSchema: META_DISCOVER_SCHEMA,
|
|
3305
|
+
async execute(input, call) {
|
|
3306
|
+
const invalid = validateEnvelope("surface_discover", META_DISCOVER_SCHEMA, input);
|
|
3307
|
+
if (invalid) {
|
|
3308
|
+
return envelopeFailure("meta:surface.discover", void 0, invalid, call.toolCallId);
|
|
3309
|
+
}
|
|
3242
3310
|
const requested = input?.scope;
|
|
3243
3311
|
const effective = intersectScope(options.scope, requested);
|
|
3244
3312
|
const snapshot = registry.snapshot({
|
|
@@ -3266,25 +3334,15 @@ function createAgentToolset(registry, options) {
|
|
|
3266
3334
|
{
|
|
3267
3335
|
name: "surface_read",
|
|
3268
3336
|
description: "[meta] Invoke an observation by capabilityId and return its output.",
|
|
3269
|
-
inputSchema:
|
|
3270
|
-
type: "object",
|
|
3271
|
-
properties: {
|
|
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
|
-
}
|
|
3280
|
-
},
|
|
3281
|
-
required: ["capabilityId"],
|
|
3282
|
-
additionalProperties: false
|
|
3283
|
-
},
|
|
3337
|
+
inputSchema: META_READ_SCHEMA,
|
|
3284
3338
|
async execute(input, call) {
|
|
3285
|
-
const req = input;
|
|
3339
|
+
const req = input ?? {};
|
|
3340
|
+
const invalid = validateEnvelope("surface_read", META_READ_SCHEMA, input);
|
|
3341
|
+
if (invalid) {
|
|
3342
|
+
return envelopeFailure("meta:surface.read", req.capabilityId, invalid, call.toolCallId);
|
|
3343
|
+
}
|
|
3286
3344
|
const snapshot = snapshotFor();
|
|
3287
|
-
const registrationId =
|
|
3345
|
+
const { registrationId } = findTarget(snapshot, req.capabilityId, req.instanceId);
|
|
3288
3346
|
return invokeThroughSurface(
|
|
3289
3347
|
{
|
|
3290
3348
|
capabilityId: req.capabilityId,
|
|
@@ -3302,38 +3360,27 @@ function createAgentToolset(registry, options) {
|
|
|
3302
3360
|
{
|
|
3303
3361
|
name: "surface_act",
|
|
3304
3362
|
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.",
|
|
3305
|
-
inputSchema:
|
|
3306
|
-
type: "object",
|
|
3307
|
-
properties: {
|
|
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
|
-
}
|
|
3329
|
-
},
|
|
3330
|
-
required: ["capabilityId"],
|
|
3331
|
-
additionalProperties: false
|
|
3332
|
-
},
|
|
3363
|
+
inputSchema: META_ACT_SCHEMA,
|
|
3333
3364
|
async execute(input, call) {
|
|
3334
|
-
const req = input;
|
|
3365
|
+
const req = input ?? {};
|
|
3366
|
+
const invalid = validateEnvelope("surface_act", META_ACT_SCHEMA, input, ["input"]);
|
|
3367
|
+
if (invalid) {
|
|
3368
|
+
return envelopeFailure("meta:surface.act", req.capabilityId, invalid, call.toolCallId);
|
|
3369
|
+
}
|
|
3335
3370
|
const snapshot = snapshotFor();
|
|
3336
|
-
const registrationId =
|
|
3371
|
+
const { registrationId, inputSchema } = findTarget(
|
|
3372
|
+
snapshot,
|
|
3373
|
+
req.capabilityId,
|
|
3374
|
+
req.instanceId
|
|
3375
|
+
);
|
|
3376
|
+
let actInput = req.input;
|
|
3377
|
+
const parsed = parseStringifiedObject(actInput, inputSchema);
|
|
3378
|
+
if (parsed !== void 0) {
|
|
3379
|
+
actInput = parsed;
|
|
3380
|
+
devWarn(
|
|
3381
|
+
`[agent-surface] surface_act got \`input\` as a JSON-encoded string for "${req.capabilityId}" and parsed it; the provider is not honoring the tool schema.`
|
|
3382
|
+
);
|
|
3383
|
+
}
|
|
3337
3384
|
return invokeThroughSurface(
|
|
3338
3385
|
{
|
|
3339
3386
|
capabilityId: req.capabilityId,
|
|
@@ -3342,7 +3389,7 @@ function createAgentToolset(registry, options) {
|
|
|
3342
3389
|
surfaceVersion: req.surfaceVersion ?? snapshot.surfaceVersion,
|
|
3343
3390
|
kind: "action"
|
|
3344
3391
|
},
|
|
3345
|
-
|
|
3392
|
+
actInput,
|
|
3346
3393
|
call.toolCallId,
|
|
3347
3394
|
{
|
|
3348
3395
|
...req.invocationId !== void 0 ? { invocationId: req.invocationId } : {},
|
|
@@ -3436,7 +3483,7 @@ function intersectScope(floor, requested) {
|
|
|
3436
3483
|
}
|
|
3437
3484
|
return out.size > 0 ? { scope: [...out], empty: false, rejected } : { empty: true, rejected };
|
|
3438
3485
|
}
|
|
3439
|
-
function
|
|
3486
|
+
function findTarget(snapshot, capabilityId, instanceId) {
|
|
3440
3487
|
const matches = [];
|
|
3441
3488
|
for (const component of snapshot.components) {
|
|
3442
3489
|
if (instanceId !== void 0 && component.instanceId !== instanceId) continue;
|
|
@@ -3444,12 +3491,89 @@ function findRegistrationId(snapshot, capabilityId, instanceId) {
|
|
|
3444
3491
|
...component.observations,
|
|
3445
3492
|
...component.actions
|
|
3446
3493
|
];
|
|
3447
|
-
|
|
3494
|
+
const hit = all.find((c) => c.capabilityId === capabilityId);
|
|
3495
|
+
if (hit) {
|
|
3496
|
+
matches.push({
|
|
3497
|
+
registrationId: component.registrationId,
|
|
3498
|
+
..."inputSchema" in hit ? { inputSchema: hit.inputSchema } : {}
|
|
3499
|
+
});
|
|
3500
|
+
}
|
|
3448
3501
|
}
|
|
3449
3502
|
for (const proc of snapshot.procedures) {
|
|
3450
|
-
if (proc.procedureId === capabilityId)
|
|
3503
|
+
if (proc.procedureId === capabilityId) {
|
|
3504
|
+
matches.push({ registrationId: proc.registrationId, inputSchema: proc.inputSchema });
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
return matches.length === 1 ? matches[0] : {};
|
|
3508
|
+
}
|
|
3509
|
+
function validateEnvelope(verb, schema, raw, exempt = []) {
|
|
3510
|
+
if (raw !== void 0 && raw !== null && (typeof raw !== "object" || Array.isArray(raw))) {
|
|
3511
|
+
return envelopeError(verb, [
|
|
3512
|
+
{ path: "", message: `\`${verb}\` takes a JSON object of arguments.` }
|
|
3513
|
+
]);
|
|
3514
|
+
}
|
|
3515
|
+
const properties = schema.properties ?? {};
|
|
3516
|
+
const known = Object.keys(properties);
|
|
3517
|
+
const required = schema.required ?? [];
|
|
3518
|
+
const req = raw ?? {};
|
|
3519
|
+
const issues = [];
|
|
3520
|
+
for (const key of required) {
|
|
3521
|
+
if (req[key] === void 0) issues.push({ path: key, message: `\`${key}\` is required.` });
|
|
3522
|
+
}
|
|
3523
|
+
for (const [key, value] of Object.entries(req)) {
|
|
3524
|
+
if (value === void 0) continue;
|
|
3525
|
+
if (!known.includes(key)) {
|
|
3526
|
+
if (schema.additionalProperties === false) {
|
|
3527
|
+
issues.push({
|
|
3528
|
+
path: key,
|
|
3529
|
+
// The high-value half is the pointer back at `input`: it turns the
|
|
3530
|
+
// dead end of a hoisted capability argument into a one-retry
|
|
3531
|
+
// recovery. A verb without an `input` has nowhere to point.
|
|
3532
|
+
message: `Unknown top-level property. \`${verb}\` accepts only ${known.join(", ")}.${known.includes("input") ? " An argument the capability declares belongs inside `input`." : ""}`
|
|
3533
|
+
});
|
|
3534
|
+
}
|
|
3535
|
+
continue;
|
|
3536
|
+
}
|
|
3537
|
+
if (exempt.includes(key)) continue;
|
|
3538
|
+
const issue = checkDeclaredType(key, properties[key], value);
|
|
3539
|
+
if (issue) issues.push(issue);
|
|
3540
|
+
}
|
|
3541
|
+
return issues.length > 0 ? envelopeError(verb, issues) : void 0;
|
|
3542
|
+
}
|
|
3543
|
+
function checkDeclaredType(key, property, value) {
|
|
3544
|
+
if (property.type === "string" && (typeof value !== "string" || value.length === 0)) {
|
|
3545
|
+
return { path: key, message: `\`${key}\` must be a non-empty string.` };
|
|
3546
|
+
}
|
|
3547
|
+
if (property.type === "array") {
|
|
3548
|
+
if (!Array.isArray(value)) return { path: key, message: `\`${key}\` must be an array.` };
|
|
3549
|
+
const items = property.items;
|
|
3550
|
+
if (items?.type === "string" && !value.every((item) => typeof item === "string")) {
|
|
3551
|
+
return { path: key, message: `\`${key}\` must be an array of strings.` };
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
return void 0;
|
|
3555
|
+
}
|
|
3556
|
+
function envelopeError(verb, issues) {
|
|
3557
|
+
return {
|
|
3558
|
+
code: "INVALID_INPUT",
|
|
3559
|
+
// Names the envelope, not the capability: pointing the model at the
|
|
3560
|
+
// capability's schema when the wrapper is what is wrong sends it to fix
|
|
3561
|
+
// something that is already correct.
|
|
3562
|
+
message: `The \`${verb}\` call is malformed \u2014 the fault is in the tool's own arguments, not the capability's input. Fix the listed issues and retry.`,
|
|
3563
|
+
retry: "with-changes",
|
|
3564
|
+
details: { issues }
|
|
3565
|
+
};
|
|
3566
|
+
}
|
|
3567
|
+
function parseStringifiedObject(value, targetSchema) {
|
|
3568
|
+
if (typeof value !== "string" || targetSchema?.type !== "object") return void 0;
|
|
3569
|
+
let parsed;
|
|
3570
|
+
try {
|
|
3571
|
+
parsed = JSON.parse(value);
|
|
3572
|
+
} catch {
|
|
3573
|
+
return void 0;
|
|
3451
3574
|
}
|
|
3452
|
-
|
|
3575
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return void 0;
|
|
3576
|
+
return parsed;
|
|
3453
3577
|
}
|
|
3454
3578
|
export {
|
|
3455
3579
|
AGENT_CAPABILITY_ERROR_CODES,
|
|
@@ -3490,7 +3614,6 @@ export {
|
|
|
3490
3614
|
parseCapabilityId,
|
|
3491
3615
|
rateLimit,
|
|
3492
3616
|
requireConfirmation,
|
|
3493
|
-
stableDescriptionOf,
|
|
3494
3617
|
tenantBoundary,
|
|
3495
3618
|
validateComponentDefinition,
|
|
3496
3619
|
validateJsonSchemaDocument,
|