@silvana-one/coordination
Version:
Silvana Coordination Client
543 lines • 21.4 kB
JavaScript
import { Transaction } from "@mysten/sui/transactions";
import { SUI_CLOCK_OBJECT_ID } from "@mysten/sui/utils";
import { fetchSuiDynamicField, fetchSuiDynamicFieldsList } from "./fetch.js";
import { silvanaRegistryPackage } from "./package.js";
export class AgentRegistry {
constructor(params) {
this.registry = params.registry;
}
static createAgentRegistry(params) {
const { name, transaction } = params;
console.log("Creating agent registry", name);
const tx = transaction ?? new Transaction();
tx.moveCall({
target: `${silvanaRegistryPackage}::registry::create_registry`,
arguments: [tx.pure.string(name)],
});
return tx;
}
createDeveloper(params) {
const { name, developerOwner, github, image, description, site, transaction } = params;
const tx = transaction ?? new Transaction();
tx.moveCall({
target: `${silvanaRegistryPackage}::registry::add_developer`,
arguments: [
tx.object(this.registry),
tx.pure.address(developerOwner),
tx.pure.string(name),
tx.pure.string(github),
tx.pure.option("string", image ?? null),
tx.pure.option("string", description ?? null),
tx.pure.option("string", site ?? null),
tx.object(SUI_CLOCK_OBJECT_ID),
],
});
return tx;
}
updateDeveloper(params) {
const { name, github, image, description, site, transaction } = params;
const tx = transaction ?? new Transaction();
tx.moveCall({
target: `${silvanaRegistryPackage}::registry::update_developer`,
arguments: [
tx.object(this.registry),
tx.pure.string(name),
tx.pure.string(github),
tx.pure.option("string", image ?? null),
tx.pure.option("string", description ?? null),
tx.pure.option("string", site ?? null),
tx.object(SUI_CLOCK_OBJECT_ID),
],
});
return tx;
}
removeDeveloper(params) {
const { name, agentNames, transaction } = params;
const tx = transaction ?? new Transaction();
tx.moveCall({
target: `${silvanaRegistryPackage}::registry::remove_developer`,
arguments: [
tx.object(this.registry),
tx.pure.string(name),
tx.pure.vector("string", agentNames),
tx.object(SUI_CLOCK_OBJECT_ID),
],
});
return tx;
}
createAgent(params) {
const { developer, name, image, description, site, chains, transaction } = params;
const tx = transaction ?? new Transaction();
tx.moveCall({
target: `${silvanaRegistryPackage}::registry::add_agent`,
arguments: [
tx.object(this.registry),
tx.pure.string(developer),
tx.pure.string(name),
tx.pure.option("string", image ?? null),
tx.pure.option("string", description ?? null),
tx.pure.option("string", site ?? null),
tx.pure.vector("string", chains),
tx.object(SUI_CLOCK_OBJECT_ID),
],
});
return tx;
}
updateAgent(params) {
const { developer, name, image, description, site, chains, transaction } = params;
const tx = transaction ?? new Transaction();
tx.moveCall({
target: `${silvanaRegistryPackage}::registry::update_agent`,
arguments: [
tx.object(this.registry),
tx.pure.string(developer),
tx.pure.string(name),
tx.pure.option("string", image ?? null),
tx.pure.option("string", description ?? null),
tx.pure.option("string", site ?? null),
tx.pure.vector("string", chains),
tx.object(SUI_CLOCK_OBJECT_ID),
],
});
return tx;
}
removeAgent(params) {
const { developer, agent, transaction } = params;
const tx = transaction ?? new Transaction();
tx.moveCall({
target: `${silvanaRegistryPackage}::registry::remove_agent`,
arguments: [
tx.object(this.registry),
tx.pure.string(developer),
tx.pure.string(agent),
tx.object(SUI_CLOCK_OBJECT_ID),
],
});
return tx;
}
addAgentMethod(params) {
const { developer, agent, method, dockerImage, dockerSha256, minMemoryGb, minCpuCores, requiresTee, transaction, } = params;
const tx = transaction ?? new Transaction();
tx.moveCall({
target: `${silvanaRegistryPackage}::registry::add_method`,
arguments: [
tx.object(this.registry),
tx.pure.string(developer),
tx.pure.string(agent),
tx.pure.string(method),
tx.pure.string(dockerImage),
tx.pure.option("string", dockerSha256 ?? null),
tx.pure.u16(minMemoryGb),
tx.pure.u16(minCpuCores),
tx.pure.bool(requiresTee),
tx.object(SUI_CLOCK_OBJECT_ID),
],
});
return tx;
}
updateAgentMethod(params) {
const { developer, agent, method, dockerImage, dockerSha256, minMemoryGb, minCpuCores, requiresTee, transaction, } = params;
const tx = transaction ?? new Transaction();
tx.moveCall({
target: `${silvanaRegistryPackage}::registry::update_method`,
arguments: [
tx.object(this.registry),
tx.pure.string(developer),
tx.pure.string(agent),
tx.pure.string(method),
tx.pure.string(dockerImage),
tx.pure.option("string", dockerSha256 ?? null),
tx.pure.u16(minMemoryGb),
tx.pure.u16(minCpuCores),
tx.pure.bool(requiresTee),
tx.object(SUI_CLOCK_OBJECT_ID),
],
});
return tx;
}
removeAgentMethod(params) {
const { developer, agent, method, transaction } = params;
const tx = transaction ?? new Transaction();
tx.moveCall({
target: `${silvanaRegistryPackage}::registry::remove_method`,
arguments: [
tx.object(this.registry),
tx.pure.string(developer),
tx.pure.string(agent),
tx.pure.string(method),
tx.object(SUI_CLOCK_OBJECT_ID),
],
});
return tx;
}
addMethodToApp(params) {
const { appName, methodName, description, developerName, agentName, agentMethod, transaction, } = params;
const tx = transaction ?? new Transaction();
// Create the app method using app_method::new
const appMethod = tx.moveCall({
target: `${silvanaRegistryPackage}::app_method::new`,
arguments: [
tx.pure.option("string", description ?? null),
tx.pure.string(developerName),
tx.pure.string(agentName),
tx.pure.string(agentMethod),
],
});
// Add the method to the app
tx.moveCall({
target: `${silvanaRegistryPackage}::registry::add_method_to_app`,
arguments: [
tx.object(this.registry),
tx.pure.string(appName),
tx.pure.string(methodName),
appMethod,
],
});
return tx;
}
addMetadata(params) {
const { appInstanceId, key, value, transaction } = params;
const tx = transaction ?? new Transaction();
tx.moveCall({
target: `${silvanaRegistryPackage}::app_instance::add_metadata`,
arguments: [
tx.object(appInstanceId),
tx.pure.string(key),
tx.pure.string(value),
],
});
return tx;
}
setDefaultMethod(params) {
const { developer, agent, method, transaction } = params;
const tx = transaction ?? new Transaction();
tx.moveCall({
target: `${silvanaRegistryPackage}::registry::set_default_method`,
arguments: [
tx.object(this.registry),
tx.pure.string(developer),
tx.pure.string(agent),
tx.pure.string(method),
tx.object(SUI_CLOCK_OBJECT_ID),
],
});
return tx;
}
removeDefaultMethod(params) {
const { developer, agent, transaction } = params;
const tx = transaction ?? new Transaction();
tx.moveCall({
target: `${silvanaRegistryPackage}::registry::remove_default_method`,
arguments: [
tx.object(this.registry),
tx.pure.string(developer),
tx.pure.string(agent),
tx.object(SUI_CLOCK_OBJECT_ID),
],
});
return tx;
}
async getDeveloper(params) {
const developerObject = await fetchSuiDynamicField({
objectID: this.registry,
fieldName: "developers",
type: "0x1::string::String",
key: params.name,
});
if (!developerObject) {
return undefined;
}
let agents = [];
const agentsObject = developerObject?.agents?.fields?.id?.id;
if (agentsObject) {
const agentsList = await fetchSuiDynamicFieldsList(agentsObject);
const agentsArray = agentsList?.data;
if (Array.isArray(agentsArray)) {
agents = agentsArray
.map((agent) => agent?.name?.value)
.filter((agent) => agent !== undefined && typeof agent === "string");
}
}
const developer = {
id: developerObject?.id?.id,
name: developerObject.name,
github: developerObject.github,
image: developerObject?.image ?? undefined,
description: developerObject?.description ?? undefined,
site: developerObject?.site ?? undefined,
owner: developerObject.owner,
agents,
createdAt: Number(developerObject.created_at),
updatedAt: Number(developerObject.updated_at),
version: Number(developerObject.version),
};
if (!developer.id ||
!developer.name ||
!developer.github ||
!developer.owner ||
!developer.createdAt ||
!developer.updatedAt) {
return undefined;
}
return developer;
}
async getDeveloperNames(params) {
const developerObject = await fetchSuiDynamicField({
objectID: this.registry,
fieldName: "developers_index",
type: "address",
key: params.developerAddress,
});
if (!developerObject) {
return undefined;
}
const developer = {
id: developerObject?.id?.id,
developer_address: developerObject.developer,
names: developerObject.names,
version: Number(developerObject.version),
};
if (!developer.id || !developer.developer_address || !developer.names) {
return undefined;
}
return developer;
}
async getAgent(params) {
const developerObject = await fetchSuiDynamicField({
objectID: this.registry,
fieldName: "developers",
type: "0x1::string::String",
key: params.developer,
});
const id = developerObject?.agents?.fields?.id?.id;
if (!id) {
return undefined;
}
const agentObject = await fetchSuiDynamicField({
parentID: id,
fieldName: "agents",
type: "0x1::string::String",
key: params.agent,
});
if (!agentObject) {
return undefined;
}
// Parse methods from VecMap structure
const methods = {};
const methodsData = agentObject?.methods?.fields?.contents;
if (methodsData && Array.isArray(methodsData)) {
for (const entry of methodsData) {
const key = entry?.fields?.key;
const value = entry?.fields?.value;
if (key && value) {
methods[key] = {
dockerImage: value.docker_image,
dockerSha256: value.docker_sha256 ?? undefined,
minMemoryGb: Number(value.min_memory_gb),
minCpuCores: Number(value.min_cpu_cores),
requiresTee: Boolean(value.requires_tee),
};
}
}
}
// Parse default method if it exists
let defaultMethod;
const defaultMethodData = agentObject?.default_method;
if (defaultMethodData &&
typeof defaultMethodData === "object" &&
!Array.isArray(defaultMethodData)) {
defaultMethod = {
dockerImage: defaultMethodData.docker_image,
dockerSha256: defaultMethodData.docker_sha256 ?? undefined,
minMemoryGb: Number(defaultMethodData.min_memory_gb),
minCpuCores: Number(defaultMethodData.min_cpu_cores),
requiresTee: Boolean(defaultMethodData.requires_tee),
};
}
const agent = {
id: agentObject?.id?.id,
name: agentObject.name,
image: agentObject?.image ?? undefined,
description: agentObject?.description ?? undefined,
site: agentObject?.site ?? undefined,
chains: agentObject?.chains ?? [],
methods,
defaultMethod,
createdAt: Number(agentObject.created_at),
updatedAt: Number(agentObject.updated_at),
version: Number(agentObject.version),
};
// Only check for essential fields
if (!agent.id || !agent.name) {
return undefined;
}
return agent;
}
async getApp(params) {
const appObject = await fetchSuiDynamicField({
objectID: this.registry,
fieldName: "apps",
type: "0x1::string::String",
key: params.name,
});
if (!appObject) {
return undefined;
}
// Parse methods from VecMap structure
const methods = {};
const methodsData = appObject?.methods?.fields?.contents;
if (methodsData && Array.isArray(methodsData)) {
for (const entry of methodsData) {
const key = entry?.fields?.key;
const value = entry?.fields?.value?.fields;
if (key && value) {
methods[key] = {
description: value.description ?? undefined,
developer: value.developer,
agent: value.agent,
agentMethod: value.agent_method,
};
}
}
}
// Parse instances from VecSet structure
const instances = [];
const instancesData = appObject?.instances?.fields?.contents;
if (instancesData && Array.isArray(instancesData)) {
for (const instance of instancesData) {
if (instance?.fields?.key) {
instances.push(instance.fields.key);
}
}
}
const app = {
id: appObject?.id?.id,
name: appObject.name,
description: appObject?.description ?? undefined,
methods,
owner: appObject.owner,
createdAt: Number(appObject.created_at),
updatedAt: Number(appObject.updated_at),
version: Number(appObject.version),
instances,
};
// Check for essential fields
if (!app.id || !app.name || !app.owner) {
return undefined;
}
return app;
}
createApp(params) {
const { name, owner, description, transaction } = params;
const tx = transaction ?? new Transaction();
tx.moveCall({
target: `${silvanaRegistryPackage}::registry::add_app`,
arguments: [
tx.object(this.registry),
tx.pure.string(name),
tx.pure.address(owner),
tx.pure.option("string", description ?? null),
tx.object(SUI_CLOCK_OBJECT_ID),
],
});
return tx;
}
removeApp(params) {
const { name, transaction } = params;
const tx = transaction ?? new Transaction();
tx.moveCall({
target: `${silvanaRegistryPackage}::registry::remove_app`,
arguments: [
tx.object(this.registry),
tx.pure.string(name),
tx.object(SUI_CLOCK_OBJECT_ID),
],
});
return tx;
}
static async getDockerImageDetails(params) {
try {
const { dockerImage } = params;
// Parse image_source to extract repository and tag
const colonPos = dockerImage.lastIndexOf(":");
const repository = colonPos !== -1 ? dockerImage.slice(0, colonPos) : dockerImage;
const tag = colonPos !== -1 ? dockerImage.slice(colonPos + 1) : "latest";
// 1. Get token
const tokenResponse = await fetch("https://auth.docker.io/token?" +
new URLSearchParams({
service: "registry.docker.io",
scope: `repository:${repository}:pull`,
}));
if (!tokenResponse.ok) {
return undefined;
}
const tokenData = await tokenResponse.json();
const token = tokenData.token;
if (!token) {
return undefined;
}
// 2. Fetch manifest/index
const manifestResponse = await fetch(`https://registry-1.docker.io/v2/${repository}/manifests/${tag}`, {
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.v2+json",
},
});
if (!manifestResponse.ok) {
return undefined;
}
const contentType = manifestResponse.headers.get("content-type") || "";
// Extract the digest from the response headers
let digest = manifestResponse.headers.get("docker-content-digest") || "";
let manifest;
if (contentType.includes("index") || contentType.includes("list")) {
// This is a manifest index (multi-platform)
const idx = await manifestResponse.json();
// Pick amd64/linux manifest
const platformManifest = idx.manifests?.find((m) => m.platform?.architecture === "amd64" && m.platform?.os === "linux");
if (!platformManifest) {
return undefined;
}
const platformDigest = platformManifest.digest;
// 3. Fetch the actual manifest
const actualManifestResponse = await fetch(`https://registry-1.docker.io/v2/${repository}/manifests/${platformDigest}`, {
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/vnd.docker.distribution.manifest.v2+json",
},
});
if (!actualManifestResponse.ok) {
return undefined;
}
manifest = await actualManifestResponse.json();
// Update digest from the actual manifest response
const actualDigest = actualManifestResponse.headers.get("docker-content-digest");
if (actualDigest) {
digest = actualDigest;
}
}
else {
// This is already a direct manifest (single platform)
manifest = await manifestResponse.json();
}
if (!manifest?.layers || !Array.isArray(manifest.layers)) {
return undefined;
}
const numberOfLayers = manifest.layers.length;
// Remove the "sha256:" prefix if present
const sha256 = digest.startsWith("sha256:") ? digest.slice(7) : digest;
if (!sha256) {
return undefined;
}
return {
sha256,
numberOfLayers,
};
}
catch (error) {
console.error("Error fetching Docker image details:", error);
return undefined;
}
}
}
//# sourceMappingURL=agent.js.map