mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-10 00:14:38 +00:00
Manifest-backed MCP tools, prompts, and resources with surface affordance contracts; agent capability manifest and governance projection; API contract tests and capability route projection; operations-loop and intelligence-funnel telemetry; release-control subsystem documentation, registry, and tooling; licensing and configuration.
392 lines
15 KiB
Go
392 lines
15 KiB
Go
package agentcapabilities
|
|
|
|
import "strings"
|
|
|
|
const (
|
|
SurfaceIDPulseAssistant = "pulse_assistant"
|
|
SurfaceIDPulseMCP = "pulse_mcp"
|
|
|
|
SurfaceToolSourceAssistantRegistry = "assistant_registry"
|
|
SurfaceToolSourceCapabilityManifest = "capability_manifest"
|
|
)
|
|
|
|
// SurfaceToolContract describes the tool vocabulary projected for one
|
|
// operator-facing Pulse Intelligence surface. Assistant tools come from the
|
|
// native registry/provider-tool projection, while external-adapter tools come
|
|
// from the canonical manifest capability projection; keeping the source
|
|
// explicit stops supported surfaces from being treated as competing duplicate
|
|
// implementations.
|
|
type SurfaceToolContract struct {
|
|
SurfaceID string `json:"surfaceId"`
|
|
SurfaceLabel string `json:"surfaceLabel,omitempty"`
|
|
ToolSource string `json:"toolSource"`
|
|
ToolNames []string `json:"toolNames"`
|
|
RegistryToolNames []string `json:"registryToolNames,omitempty"`
|
|
CapabilityNames []string `json:"capabilityNames,omitempty"`
|
|
NativeToolNames []string `json:"nativeToolNames,omitempty"`
|
|
Affordances SurfaceAffordanceContract `json:"affordances,omitempty"`
|
|
}
|
|
|
|
// ResolvedSurfaceAffordanceContract is the normalized surface identity and
|
|
// affordance set that prompts, adapters, docs, and UI projections should use
|
|
// when they need one surface's supported Pulse Intelligence affordances.
|
|
type ResolvedSurfaceAffordanceContract struct {
|
|
SurfaceID string
|
|
SurfaceLabel string
|
|
Affordances SurfaceAffordanceContract
|
|
}
|
|
|
|
// NormalizeCollections detaches slice fields and preserves a stable empty
|
|
// toolNames collection for JSON surfaces.
|
|
func (c SurfaceToolContract) NormalizeCollections() SurfaceToolContract {
|
|
c.ToolNames = append([]string(nil), c.ToolNames...)
|
|
if c.ToolNames == nil {
|
|
c.ToolNames = []string{}
|
|
}
|
|
c.RegistryToolNames = append([]string(nil), c.RegistryToolNames...)
|
|
c.CapabilityNames = append([]string(nil), c.CapabilityNames...)
|
|
c.NativeToolNames = append([]string(nil), c.NativeToolNames...)
|
|
return c
|
|
}
|
|
|
|
// CloneSurfaceToolContracts returns detached surface tool summaries in their
|
|
// original projection order.
|
|
func CloneSurfaceToolContracts(contracts []SurfaceToolContract) []SurfaceToolContract {
|
|
cloned := make([]SurfaceToolContract, 0, len(contracts))
|
|
for _, contract := range contracts {
|
|
cloned = append(cloned, contract.NormalizeCollections())
|
|
}
|
|
return cloned
|
|
}
|
|
|
|
// DefaultSurfaceLabelForID returns the compatibility label for known Pulse
|
|
// Intelligence surfaces when older manifest payloads omit a surfaceContract
|
|
// block.
|
|
func DefaultSurfaceLabelForID(surfaceID string) string {
|
|
switch strings.TrimSpace(surfaceID) {
|
|
case SurfaceIDPulseAssistant:
|
|
return "Pulse Assistant"
|
|
case SurfaceIDPulseMCP:
|
|
return "Pulse MCP"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// DefaultSurfaceAffordancesForID returns compatibility defaults for known
|
|
// Pulse Intelligence surfaces when older manifest payloads do not declare the
|
|
// affordance block.
|
|
func DefaultSurfaceAffordancesForID(surfaceID string) SurfaceAffordanceContract {
|
|
switch strings.TrimSpace(surfaceID) {
|
|
case SurfaceIDPulseAssistant:
|
|
return SurfaceAffordanceContract{
|
|
Tools: true,
|
|
InteractiveQuestions: true,
|
|
}
|
|
case SurfaceIDPulseMCP:
|
|
return SurfaceAffordanceContract{
|
|
Tools: true,
|
|
Resources: true,
|
|
Prompts: true,
|
|
CapabilityMetadata: true,
|
|
}
|
|
default:
|
|
return SurfaceAffordanceContract{}
|
|
}
|
|
}
|
|
|
|
// SurfaceAffordancesDeclared reports whether any affordance was explicitly
|
|
// enabled. It intentionally treats an all-false struct as missing/legacy for
|
|
// the known current surfaces, because older JSON payloads had no affordance
|
|
// field at all.
|
|
func SurfaceAffordancesDeclared(affordances SurfaceAffordanceContract) bool {
|
|
return affordances.Tools ||
|
|
affordances.Resources ||
|
|
affordances.Prompts ||
|
|
affordances.CapabilityMetadata ||
|
|
affordances.InteractiveQuestions
|
|
}
|
|
|
|
// NormalizeSurfaceAffordances fills compatibility defaults for known surfaces
|
|
// while preserving explicit manifest-owned affordances when present.
|
|
func NormalizeSurfaceAffordances(surface OperatorSurfaceContract) SurfaceAffordanceContract {
|
|
if SurfaceAffordancesDeclared(surface.Affordances) {
|
|
return surface.Affordances
|
|
}
|
|
return DefaultSurfaceAffordancesForID(surface.ID)
|
|
}
|
|
|
|
// ResolveSurfaceAffordanceContract resolves one operator surface from the
|
|
// manifest surface contract and applies compatibility defaults for known first-
|
|
// party surfaces. The returned bool reports whether the surface was declared in
|
|
// the provided contract; callers can still use the returned compatibility
|
|
// defaults when false.
|
|
func ResolveSurfaceAffordanceContract(contract SurfaceContract, surfaceID, surfaceLabelFallback string) (ResolvedSurfaceAffordanceContract, bool) {
|
|
surfaceID = strings.TrimSpace(surfaceID)
|
|
if surfaceID == "" {
|
|
return ResolvedSurfaceAffordanceContract{}, false
|
|
}
|
|
|
|
lookupLabel := strings.TrimSpace(surfaceLabelFallback)
|
|
if lookupLabel == "" {
|
|
lookupLabel = DefaultSurfaceLabelForID(surfaceID)
|
|
}
|
|
label := lookupLabel
|
|
if label == "" {
|
|
label = surfaceID
|
|
}
|
|
|
|
if surface, ok := FindOperatorSurfaceContract(contract, surfaceID, lookupLabel); ok {
|
|
resolvedID := strings.TrimSpace(surface.ID)
|
|
if resolvedID == "" {
|
|
resolvedID = surfaceID
|
|
}
|
|
resolvedLabel := strings.TrimSpace(surface.Label)
|
|
if resolvedLabel == "" {
|
|
resolvedLabel = label
|
|
}
|
|
return ResolvedSurfaceAffordanceContract{
|
|
SurfaceID: resolvedID,
|
|
SurfaceLabel: resolvedLabel,
|
|
Affordances: NormalizeSurfaceAffordances(surface),
|
|
}, true
|
|
}
|
|
|
|
return ResolvedSurfaceAffordanceContract{
|
|
SurfaceID: surfaceID,
|
|
SurfaceLabel: label,
|
|
Affordances: DefaultSurfaceAffordancesForID(surfaceID),
|
|
}, false
|
|
}
|
|
|
|
// ManifestSurfaceAffordances resolves the affordance contract for one manifest
|
|
// surface. Known first-party surfaces retain compatibility defaults for older
|
|
// manifests that predate the surfaceContract block.
|
|
func ManifestSurfaceAffordances(manifest Manifest, surfaceID string) (SurfaceAffordanceContract, bool) {
|
|
resolution, ok := ResolveSurfaceAffordanceContract(manifest.SurfaceContract, surfaceID, "")
|
|
if SurfaceAffordancesDeclared(resolution.Affordances) {
|
|
return resolution.Affordances, ok
|
|
}
|
|
return SurfaceAffordanceContract{}, false
|
|
}
|
|
|
|
// FindOperatorSurfaceContract resolves one manifest-owned operator surface by
|
|
// id or label.
|
|
func FindOperatorSurfaceContract(contract SurfaceContract, surfaceID, surfaceName string) (OperatorSurfaceContract, bool) {
|
|
surfaceID = strings.TrimSpace(surfaceID)
|
|
surfaceName = strings.TrimSpace(surfaceName)
|
|
for _, surface := range contract.OperatorSurfaces {
|
|
if OperatorSurfaceContractMatches(surface, surfaceID, surfaceName) {
|
|
return surface, true
|
|
}
|
|
}
|
|
return OperatorSurfaceContract{}, false
|
|
}
|
|
|
|
// OperatorSurfaceContractMatches reports whether a surface matches by stable id
|
|
// or display label.
|
|
func OperatorSurfaceContractMatches(surface OperatorSurfaceContract, surfaceID, surfaceName string) bool {
|
|
if surfaceID != "" && strings.EqualFold(strings.TrimSpace(surface.ID), surfaceID) {
|
|
return true
|
|
}
|
|
if surfaceName != "" && strings.EqualFold(strings.TrimSpace(surface.Label), surfaceName) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// SurfaceAffordanceLabels formats enabled affordances with stable,
|
|
// operator-facing labels.
|
|
func SurfaceAffordanceLabels(affordances SurfaceAffordanceContract) []string {
|
|
labels := []string{}
|
|
if affordances.Tools {
|
|
labels = append(labels, "tools")
|
|
}
|
|
if affordances.Resources {
|
|
labels = append(labels, "resources")
|
|
}
|
|
if affordances.Prompts {
|
|
labels = append(labels, "prompts")
|
|
}
|
|
if affordances.CapabilityMetadata {
|
|
labels = append(labels, "capability metadata")
|
|
}
|
|
if affordances.InteractiveQuestions {
|
|
labels = append(labels, "interactive questions")
|
|
}
|
|
return labels
|
|
}
|
|
|
|
// ProjectPulseAssistantSurfaceToolContract projects the native Assistant
|
|
// provider-tool list into the shared surface summary. Registry-backed tools and
|
|
// Assistant-native interaction tools are separated deliberately: pulse_question
|
|
// is a native in-app affordance, not an MCP capability.
|
|
func ProjectPulseAssistantSurfaceToolContract(contract SurfaceContract, providerTools []ProviderTool) (SurfaceToolContract, bool) {
|
|
surface, ok := ResolveSurfaceAffordanceContract(contract, SurfaceIDPulseAssistant, "")
|
|
|
|
toolNames := ProviderToolNames(providerTools)
|
|
if !surface.Affordances.Tools {
|
|
toolNames = []string{}
|
|
}
|
|
nativeCatalog := NewProviderToolNameCatalog(AssistantNativeProviderToolNames())
|
|
registryNames := make([]string, 0, len(toolNames))
|
|
nativeNames := make([]string, 0, len(toolNames))
|
|
offeredNames := make([]string, 0, len(toolNames))
|
|
for _, name := range toolNames {
|
|
if nativeCatalog.Has(name) {
|
|
if !surface.Affordances.InteractiveQuestions {
|
|
continue
|
|
}
|
|
nativeNames = append(nativeNames, name)
|
|
offeredNames = append(offeredNames, name)
|
|
continue
|
|
}
|
|
registryNames = append(registryNames, name)
|
|
offeredNames = append(offeredNames, name)
|
|
}
|
|
|
|
return SurfaceToolContract{
|
|
SurfaceID: surface.SurfaceID,
|
|
SurfaceLabel: surface.SurfaceLabel,
|
|
ToolSource: SurfaceToolSourceAssistantRegistry,
|
|
ToolNames: offeredNames,
|
|
RegistryToolNames: registryNames,
|
|
NativeToolNames: nativeNames,
|
|
Affordances: surface.Affordances,
|
|
}.NormalizeCollections(), ok
|
|
}
|
|
|
|
// ProjectManifestSurfaceToolContract normalizes a manifest-published
|
|
// request/response tool allowlist into the shared surface summary for a
|
|
// manifest-declared external adapter. Streaming capabilities remain represented
|
|
// by resources, prompts, or notifications, not request/response tool entries.
|
|
func ProjectManifestSurfaceToolContract(manifest Manifest, surfaceID string) (SurfaceToolContract, bool) {
|
|
surfaceID = strings.TrimSpace(surfaceID)
|
|
if surfaceID == "" {
|
|
return SurfaceToolContract{}, false
|
|
}
|
|
surface, ok := FindOperatorSurfaceContract(manifest.SurfaceContract, surfaceID, "")
|
|
if !ok || !surface.ExternalAdapter {
|
|
return SurfaceToolContract{}, false
|
|
}
|
|
if contract, ok := FindManifestSurfaceToolContract(manifest, surface.ID); ok {
|
|
return normalizeManifestSurfaceToolContract(manifest, surface, contract), true
|
|
}
|
|
return SurfaceToolContract{}, false
|
|
}
|
|
|
|
func normalizeManifestSurfaceToolContract(manifest Manifest, surface OperatorSurfaceContract, contract SurfaceToolContract) SurfaceToolContract {
|
|
resolvedSurface, _ := ResolveSurfaceAffordanceContract(manifest.SurfaceContract, surface.ID, surface.Label)
|
|
contract = contract.NormalizeCollections()
|
|
if strings.TrimSpace(contract.SurfaceID) == "" {
|
|
contract.SurfaceID = resolvedSurface.SurfaceID
|
|
}
|
|
if strings.TrimSpace(contract.SurfaceLabel) == "" {
|
|
contract.SurfaceLabel = resolvedSurface.SurfaceLabel
|
|
}
|
|
if strings.TrimSpace(contract.ToolSource) == "" {
|
|
contract.ToolSource = SurfaceToolSourceCapabilityManifest
|
|
}
|
|
if SurfaceAffordancesDeclared(contract.Affordances) {
|
|
contract.Affordances = IntersectSurfaceAffordances(resolvedSurface.Affordances, contract.Affordances)
|
|
} else {
|
|
contract.Affordances = resolvedSurface.Affordances
|
|
}
|
|
if len(contract.ToolNames) == 0 && len(contract.CapabilityNames) > 0 {
|
|
contract.ToolNames = append([]string(nil), contract.CapabilityNames...)
|
|
}
|
|
if contract.Affordances.Tools {
|
|
toolNames := requestResponseCapabilityNamesForNames(manifest.Capabilities, contract.ToolNames)
|
|
contract.ToolNames = toolNames
|
|
contract.CapabilityNames = append([]string(nil), toolNames...)
|
|
} else {
|
|
contract.ToolNames = []string{}
|
|
contract.CapabilityNames = []string{}
|
|
}
|
|
contract.RegistryToolNames = nil
|
|
contract.NativeToolNames = nil
|
|
return contract.NormalizeCollections()
|
|
}
|
|
|
|
// IntersectSurfaceAffordances applies a surface-local narrowing contract without
|
|
// allowing that local contract to re-enable an affordance disabled by the
|
|
// operator surface.
|
|
func IntersectSurfaceAffordances(allowed SurfaceAffordanceContract, requested SurfaceAffordanceContract) SurfaceAffordanceContract {
|
|
return SurfaceAffordanceContract{
|
|
Tools: allowed.Tools && requested.Tools,
|
|
Resources: allowed.Resources && requested.Resources,
|
|
Prompts: allowed.Prompts && requested.Prompts,
|
|
CapabilityMetadata: allowed.CapabilityMetadata && requested.CapabilityMetadata,
|
|
InteractiveQuestions: allowed.InteractiveQuestions && requested.InteractiveQuestions,
|
|
}
|
|
}
|
|
|
|
// ResolveManifestSurfaceToolContract returns the normalized request/response
|
|
// surface tool contract for a manifest-backed external-agent surface. Missing
|
|
// published surface tool contracts fail closed rather than inferring tools from
|
|
// raw manifest capabilities.
|
|
func ResolveManifestSurfaceToolContract(manifest Manifest, surfaceID string) (SurfaceToolContract, bool) {
|
|
surfaceID = strings.TrimSpace(surfaceID)
|
|
if surfaceID == "" {
|
|
return SurfaceToolContract{}, false
|
|
}
|
|
return ProjectManifestSurfaceToolContract(manifest, surfaceID)
|
|
}
|
|
|
|
// ProjectManifestSurfaceToolContracts returns manifest-backed external-adapter
|
|
// surface tool summaries in manifest operator-surface order. Native Assistant
|
|
// tool availability is intentionally omitted because it is runtime-specific.
|
|
func ProjectManifestSurfaceToolContracts(manifest Manifest) []SurfaceToolContract {
|
|
contracts := make([]SurfaceToolContract, 0, len(manifest.SurfaceContract.OperatorSurfaces))
|
|
for _, surface := range manifest.SurfaceContract.OperatorSurfaces {
|
|
if !surface.ExternalAdapter || strings.TrimSpace(surface.ID) == "" {
|
|
continue
|
|
}
|
|
if contract, ok := ProjectManifestSurfaceToolContract(manifest, surface.ID); ok {
|
|
contracts = append(contracts, contract)
|
|
}
|
|
}
|
|
return contracts
|
|
}
|
|
|
|
// ProjectPulseIntelligenceSurfaceToolContracts returns the supported
|
|
// operator-facing surface tool summaries in manifest order. The function is
|
|
// intentionally a projection over the two canonical sources rather than a new
|
|
// tool registry.
|
|
func ProjectPulseIntelligenceSurfaceToolContracts(manifest Manifest, assistantTools []ProviderTool) []SurfaceToolContract {
|
|
contracts := make([]SurfaceToolContract, 0, len(manifest.SurfaceContract.OperatorSurfaces))
|
|
for _, surface := range manifest.SurfaceContract.OperatorSurfaces {
|
|
switch strings.TrimSpace(surface.ID) {
|
|
case SurfaceIDPulseAssistant:
|
|
contract, _ := ProjectPulseAssistantSurfaceToolContract(manifest.SurfaceContract, assistantTools)
|
|
contracts = append(contracts, contract)
|
|
default:
|
|
if surface.ExternalAdapter && strings.TrimSpace(surface.ID) != "" {
|
|
if contract, ok := ProjectManifestSurfaceToolContract(manifest, surface.ID); ok {
|
|
contracts = append(contracts, contract)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return contracts
|
|
}
|
|
|
|
func requestResponseCapabilityNamesForNames(capabilities []Capability, names []string) []string {
|
|
result := make([]string, 0, len(names))
|
|
seen := map[string]struct{}{}
|
|
for _, rawName := range names {
|
|
name := strings.TrimSpace(rawName)
|
|
if name == "" {
|
|
continue
|
|
}
|
|
if _, exists := seen[name]; exists {
|
|
continue
|
|
}
|
|
seen[name] = struct{}{}
|
|
if _, err := ResolveRequestResponseCapability(capabilities, name); err != nil {
|
|
continue
|
|
}
|
|
result = append(result, name)
|
|
}
|
|
return result
|
|
}
|