@sema-agent/core
Version:
Stateless, task-oriented AI agent core
89 lines (88 loc) • 4.16 kB
JavaScript
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 })),
];
}