UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

89 lines (88 loc) • 4.16 kB
import { mintNamespacePrefix } from "./protocol-naming.js"; import { MCP_NAMESPACE } from "./protocol-table.js"; export const MCP_RESOURCE_LIST_ALLOW = "list_resources"; export const MCP_RESOURCE_READ_ALLOW = "read_resource"; export const LIST_MCP_RESOURCES = "ListMcpResourcesTool"; export const READ_MCP_RESOURCE = "ReadMcpResourceTool"; export const READ_MCP_RESOURCE_DIR = "ReadMcpResourceDirTool"; const MCP_RESOURCE_FACES = [LIST_MCP_RESOURCES, READ_MCP_RESOURCE, READ_MCP_RESOURCE_DIR]; export function resourceFacesLeft(leaves) { return MCP_RESOURCE_FACES.some((name) => leaves({ name })); } export function instructionsTaught(leaves, serverName, held, listed) { const own = new Set(toolNamesOfServer(held.map((t) => t.name), serverName)); return held.some((t) => (own.has(t.name) || MCP_RESOURCE_FACES.includes(t.name)) && leaves(t)) || (toolNamesOfServer(listed.map((t) => t.name), serverName).length === 0 && resourceFacesLeft(leaves)); } const ledgers = new WeakMap(); function ledgerOf(lane) { let ledger = ledgers.get(lane); if (ledger === undefined) ledgers.set(lane, (ledger = new Map())); return ledger; } const queuedFor = (lane, server) => lane.pendingAdds.some((e) => e.server === server); export function instructionsTold(lane, server) { const state = ledgerOf(lane).get(server); return state === "context" || (state === "queued" && !queuedFor(lane, server)); } export function toldOnDial(lane, server) { ledgerOf(lane).set(server, "context"); } export function toldAtStart(lane, servers) { const ledger = ledgerOf(lane); ledger.clear(); for (let i = lane.pendingRemovals.length - 1; i >= 0; i--) if (!servers.has(lane.pendingRemovals[i])) lane.pendingRemovals.splice(i, 1); for (const server of servers) if (!lane.pendingRemovals.includes(server)) ledger.set(server, "context"); } export function withdrawInstructionAdds(lane, server) { const told = instructionsTold(lane, server); for (let i = lane.pendingAdds.length - 1; i >= 0; i--) if (lane.pendingAdds[i].server === server) lane.pendingAdds.splice(i, 1); if (!told) ledgerOf(lane).delete(server); } export function tellInstructions(lane, entry) { const told = instructionsTold(lane, entry.server); withdrawInstructionAdds(lane, entry.server); lane.pendingAdds.push(entry); ledgerOf(lane).set(entry.server, told ? "context" : "queued"); } export function retractInstructions(lane, server) { const told = instructionsTold(lane, server); withdrawInstructionAdds(lane, server); if (told && !lane.pendingRemovals.includes(server)) lane.pendingRemovals.push(server); ledgerOf(lane).delete(server); } export function unretractInstructions(lane, server) { if (!lane.pendingRemovals.includes(server)) return; for (let i = lane.pendingRemovals.length - 1; i >= 0; i--) if (lane.pendingRemovals[i] === server) lane.pendingRemovals.splice(i, 1); ledgerOf(lane).set(server, "context"); } export function toolNamesOfServer(names, serverName) { const prefix = mintNamespacePrefix(MCP_NAMESPACE, serverName); return [...names].filter((name) => name.startsWith(prefix)); } export function mountedMcpOf(input) { const servers = (input.declared ?? []).map((server) => { const owned = new Set(toolNamesOfServer(input.roster.map((t) => t.name), server.name)); const tools = input.roster.flatMap((t) => (owned.has(t.name) && typeof t.mcpRemoteName === "string" ? [{ name: t.name, remoteName: t.mcpRemoteName }] : [])); return { server, tools }; }); const aggregate = input.roster.filter((t) => input.aggregateNames.has(t.name)).map((t) => t.name); return { servers, aggregate, ...(input.pool !== undefined ? { pool: input.pool } : {}) }; } export function mountedMcpPoolRows(mounted) { return [ ...mounted.servers.flatMap((m) => m.tools.map((t) => ({ name: t.name, ...(m.server.contentOrigin !== undefined ? { contentOrigin: m.server.contentOrigin } : {}) }))), ...mounted.aggregate.map((name) => ({ name })), ]; }