UNPKG

@silvana-one/coordination

Version:

Silvana Coordination Client

526 lines (469 loc) 14.2 kB
import { Transaction } from "@mysten/sui/transactions"; import { SUI_CLOCK_OBJECT_ID } from "@mysten/sui/utils"; import { fetchSuiDynamicField, fetchSuiDynamicFieldsList, fetchSuiObject, } from "./fetch.js"; type AgentChain = | "ethereum-mainnet" | "ethereum-seplolia" | "ethereum-holesky" | "ethereum-hoodi" | "mina-mainnet" | "mina-devnet" | "zeko-testnet" | "zeko-alphanet" | "sui-mainnet" | "sui-testnet" | "sui-devnet" | "solana-mainnet" | "solana-testnet" | "solana-devnet" | "solana-devnet" | "walrus-mainnet" | "walrus-testnet" | string; // other chains export interface Agent { id: string; name: string; image?: string; description?: string; site?: string; dockerImage: string; dockerSha256?: string; minMemoryGb: number; minCpuCores: number; supportsTEE: boolean; chains: AgentChain[]; createdAt: number; updatedAt: number; version: number; } export interface Developer { id: string; name: string; github: string; image?: string; description?: string; site?: string; owner: string; agents: string[]; createdAt: number; updatedAt: number; version: number; } export interface DeveloperNames { id: string; developer_address: string; names: string[]; version: number; } export class AgentRegistry { private readonly registry: string; constructor(params: { registry: string }) { this.registry = params.registry; } static createAgentRegistry(params: { name: string }): Transaction { console.log("Creating agent registry", params.name); const transaction = new Transaction(); transaction.moveCall({ target: `@silvana/agent::registry::create_registry`, arguments: [transaction.pure.string(params.name)], }); return transaction; } createDeveloper(params: { name: string; github: string; image?: string; description?: string; site?: string; }): Transaction { const { name, github, image, description, site } = params; const tx = new Transaction(); tx.moveCall({ target: `@silvana/agent::registry::add_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; } updateDeveloper(params: { name: string; github: string; image?: string; description?: string; site?: string; }): Transaction { const { name, github, image, description, site } = params; const tx = new Transaction(); tx.moveCall({ target: `@silvana/agent::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: { name: string; agentNames: string[] }): Transaction { const { name, agentNames } = params; const tx = new Transaction(); tx.moveCall({ target: `@silvana/agent::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: { developer: string; name: string; image?: string; description?: string; site?: string; docker_image: string; docker_sha256?: string; min_memory_gb: number; min_cpu_cores: number; supports_tee: boolean; chains: AgentChain[]; }): Transaction { const { developer, name, image, description, site, docker_image, docker_sha256, min_memory_gb, min_cpu_cores, supports_tee, chains, } = params; const tx = new Transaction(); tx.moveCall({ target: `@silvana/agent::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.string(docker_image), tx.pure.option("string", docker_sha256 ?? null), tx.pure.u16(min_memory_gb), tx.pure.u16(min_cpu_cores), tx.pure.bool(supports_tee), tx.pure.vector("string", chains), tx.object(SUI_CLOCK_OBJECT_ID), ], }); return tx; } updateAgent(params: { developer: string; name: string; image?: string; description?: string; site?: string; docker_image: string; docker_sha256?: string; min_memory_gb: number; min_cpu_cores: number; supports_tee: boolean; chains: AgentChain[]; }): Transaction { const { developer, name, image, description, site, docker_image, docker_sha256, min_memory_gb, min_cpu_cores, supports_tee, chains, } = params; const tx = new Transaction(); tx.moveCall({ target: `@silvana/agent::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.string(docker_image), tx.pure.option("string", docker_sha256 ?? null), tx.pure.u16(min_memory_gb), tx.pure.u16(min_cpu_cores), tx.pure.bool(supports_tee), tx.pure.vector("string", chains), tx.object(SUI_CLOCK_OBJECT_ID), ], }); return tx; } removeAgent(params: { developer: string; agent: string }): Transaction { const { developer, agent } = params; const tx = new Transaction(); tx.moveCall({ target: `@silvana/agent::registry::remove_agent`, arguments: [ tx.object(this.registry), tx.pure.string(developer), tx.pure.string(agent), tx.object(SUI_CLOCK_OBJECT_ID), ], }); return tx; } async getDeveloper(params: { name: string }): Promise<Developer | undefined> { const developerObject = await fetchSuiDynamicField({ objectID: this.registry, fieldName: "developers", type: "0x1::string::String", key: params.name, }); if (!developerObject) { return undefined; } let agents: string[] = []; const agentsObject = (developerObject as any)?.agents?.fields?.id?.id; if (agentsObject) { const agentsList = await fetchSuiDynamicFieldsList(agentsObject); const agentsArray = agentsList?.data as any; if (Array.isArray(agentsArray)) { agents = agentsArray .map((agent: any) => agent?.name?.value) .filter( (agent: any) => agent !== undefined && typeof agent === "string" ); } } const developer = { id: (developerObject as any)?.id?.id, name: (developerObject as any).name, github: (developerObject as any).github, image: (developerObject as any)?.image ?? undefined, description: (developerObject as any)?.description ?? undefined, site: (developerObject as any)?.site ?? undefined, owner: (developerObject as any).owner, agents, createdAt: Number((developerObject as any).created_at), updatedAt: Number((developerObject as any).updated_at), version: Number((developerObject as any).version), }; if ( !developer.id || !developer.name || !developer.github || !developer.owner || !developer.createdAt || !developer.updatedAt ) { return undefined; } return developer as Developer; } async getDeveloperNames(params: { developerAddress: string; }): Promise<DeveloperNames | undefined> { const developerObject = await fetchSuiDynamicField({ objectID: this.registry, fieldName: "developers_index", type: "address", key: params.developerAddress, }); if (!developerObject) { return undefined; } const developer = { id: (developerObject as any)?.id?.id, developer_address: (developerObject as any).developer, names: (developerObject as any).names, version: Number((developerObject as any).version), }; if (!developer.id || !developer.developer_address || !developer.names) { return undefined; } return developer as DeveloperNames; } async getAgent(params: { developer: string; agent: string; }): Promise<Agent | undefined> { const developerObject = await fetchSuiDynamicField({ objectID: this.registry, fieldName: "developers", type: "0x1::string::String", key: params.developer, }); const id = (developerObject as any)?.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; } const agent = { id: (agentObject as any)?.id?.id, name: (agentObject as any).name, image: (agentObject as any)?.image ?? undefined, description: (agentObject as any)?.description ?? undefined, site: (agentObject as any)?.site ?? undefined, dockerImage: (agentObject as any).docker_image, dockerSha256: (agentObject as any)?.docker_sha256 ?? undefined, minMemoryGb: Number((agentObject as any).min_memory_gb), minCpuCores: Number((agentObject as any).min_cpu_cores), supportsTEE: Boolean((agentObject as any).supports_tee), createdAt: Number((agentObject as any).created_at), updatedAt: Number((agentObject as any).updated_at), version: Number((agentObject as any).version), }; if ( !agent.id || !agent.name || !agent.dockerImage || !agent.minMemoryGb || !agent.minCpuCores || !agent.createdAt || !agent.updatedAt ) { return undefined; } return agent as Agent; } static async getDockerImageDetails(params: { dockerImage: string }): Promise< | { sha256: string; numberOfLayers: number; } | undefined > { 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: any; 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: any) => 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; } } }