# SPDX-License-Identifier: LSL-1.0 # Copyright (c) 2026 Markus Maiwald # Stewardship: Self Sovereign Society Foundation # # This file is part of the Nexus Sovereign Core. # See legal/LICENSE_SOVEREIGN.md for license terms. ## UTCP (Universal Tool Communication Protocol) Implementation ## ## This module implements the Universal Tool Communication Protocol for ## AI-addressable resources in NexusOS. UTCP enables seamless communication ## between: ## - nexus (system compiler) ## - nip (package manager) ## - Janus programming language ## - n8n AI agents ## - Local LLMs ## - SystemAdmin-AIs ## - Nippels (user application environments) ## - Nexters (system containers) ## ## UTCP provides a unified addressing scheme and request/response protocol ## for distributed system management and AI-driven automation. import std/[tables, json, strutils, times, options, uri, sequtils, random] import utils/resultutils as nipresult when defined(posix): import posix # UTCP Protocol Types type UTCPScheme* = enum ## UTCP protocol schemes UtcpPlain = "utcp" ## Plain UTCP (no encryption) UtcpSecure = "utcps" ## Secure UTCP (TLS encryption) UTCPResourceType* = enum ## Types of UTCP-addressable resources Nippel = "nippel" ## User application environment Nexter = "nexter" ## System container Package = "package" ## Package resource System = "system" ## System-level resource Tool = "tool" ## Tool endpoint (nexus, nip, janus) Agent = "agent" ## AI agent endpoint LLM = "llm" ## Local LLM endpoint UTCPAddress* = object ## Universal address for UTCP resources scheme*: UTCPScheme ## Protocol scheme (utcp/utcps) host*: string ## Hostname or IP address port*: Option[int] ## Optional port (default: 7777) resourceType*: UTCPResourceType ## Type of resource resourceName*: string ## Name of the resource path*: string ## Optional sub-path query*: Table[string, string] ## Query parameters UTCPMethod* = enum ## UTCP request methods GET = "GET" ## Query resource state POST = "POST" ## Modify resource state PUT = "PUT" ## Create/replace resource DELETE = "DELETE" ## Delete resource EXEC = "EXEC" ## Execute command SUBSCRIBE = "SUBSCRIBE" ## Subscribe to events UNSUBSCRIBE = "UNSUBSCRIBE" ## Unsubscribe from events UTCPRequest* = object ## UTCP request structure address*: UTCPAddress ## Target address meth*: UTCPMethod ## Request method (renamed from 'method' to avoid keyword) headers*: Table[string, string] ## Request headers payload*: JsonNode ## Request payload timestamp*: DateTime ## Request timestamp requestId*: string ## Unique request ID UTCPStatus* = enum ## UTCP response status codes Ok = 200 ## Success Created = 201 ## Resource created Accepted = 202 ## Request accepted NoContent = 204 ## Success, no content BadRequest = 400 ## Invalid request Unauthorized = 401 ## Authentication required Forbidden = 403 ## Access denied NotFound = 404 ## Resource not found MethodNotAllowed = 405 ## Method not supported Conflict = 409 ## Resource conflict InternalError = 500 ## Server error NotImplemented = 501 ## Method not implemented ServiceUnavailable = 503 ## Service unavailable UTCPResponse* = object ## UTCP response structure status*: UTCPStatus ## Response status headers*: Table[string, string] ## Response headers data*: JsonNode ## Response data timestamp*: DateTime ## Response timestamp requestId*: string ## Matching request ID UTCPError* = object of CatchableError ## UTCP-specific errors address*: string ## Address that caused error meth*: string ## Method that failed (renamed from 'method' to avoid keyword) UTCPHandler* = proc(request: UTCPRequest): Result[UTCPResponse, UTCPError] {.closure.} ## Handler function for UTCP requests UTCPServer* = object ## UTCP server for handling requests host*: string port*: int handlers*: Table[string, UTCPHandler] ## Route -> Handler mapping running*: bool # Constants const UTCP_DEFAULT_PORT* = 7777 UTCP_VERSION* = "1.0" UTCP_USER_AGENT* = "NexusOS-UTCP/1.0" # UTCP Address Functions proc newUTCPAddress*( host: string, resourceType: UTCPResourceType, resourceName: string, scheme: UTCPScheme = UtcpPlain, port: Option[int] = none(int), path: string = "", query: Table[string, string] = initTable[string, string]() ): UTCPAddress = ## Create a new UTCP address result = UTCPAddress( scheme: scheme, host: host, port: port, resourceType: resourceType, resourceName: resourceName, path: path, query: query ) proc parseUTCPAddress*(address: string): Result[UTCPAddress, string] = ## Parse a UTCP address string ## Format: utcp://host[:port]/resourceType/resourceName[/path][?query] try: let uri = parseUri(address) # Parse scheme let scheme = case uri.scheme: of "utcp": UtcpPlain of "utcps": UtcpSecure else: return err[UTCPAddress]("Invalid UTCP scheme: " & uri.scheme) # Parse host and port let host = uri.hostname let port = if uri.port != "": some(parseInt(uri.port)) else: none(int) # Parse path components let pathParts = uri.path.split('/').filterIt(it.len > 0) if pathParts.len < 2: return err[UTCPAddress]("Invalid UTCP path: must have resourceType/resourceName") # Parse resource type let resourceType = case pathParts[0]: of "nippel": Nippel of "nexter": Nexter of "package": Package of "system": System of "tool": Tool of "agent": Agent of "llm": LLM else: return err[UTCPAddress]("Invalid resource type: " & pathParts[0]) let resourceName = pathParts[1] let subPath = if pathParts.len > 2: "/" & pathParts[2..^1].join("/") else: "" # Parse query parameters var query = initTable[string, string]() for (key, value) in uri.query.decodeQuery(): query[key] = value return ok(UTCPAddress( scheme: scheme, host: host, port: port, resourceType: resourceType, resourceName: resourceName, path: subPath, query: query )) except Exception as e: return err[UTCPAddress]("Failed to parse UTCP address: " & e.msg) proc formatUTCPAddress*(address: UTCPAddress): string = ## Format a UTCP address as a string result = $address.scheme & "://" & address.host if address.port.isSome: result.add(":" & $address.port.get()) result.add("/" & $address.resourceType & "/" & address.resourceName) if address.path.len > 0: result.add(address.path) if address.query.len > 0: result.add("?") var first = true for key, value in address.query: if not first: result.add("&") result.add(encodeUrl(key) & "=" & encodeUrl(value)) first = false proc assignUTCPAddress*( resourceType: UTCPResourceType, resourceName: string, host: string = "" ): Result[UTCPAddress, string] = ## Assign a UTCP address to a resource ## If host is empty, uses local hostname try: let actualHost = if host.len > 0: host else: when defined(posix): var buf: array[256, char] if gethostname(cast[cstring](addr buf[0]), 256) == 0: $cast[cstring](addr buf[0]) else: "localhost" else: "localhost" let address = newUTCPAddress( host = actualHost, resourceType = resourceType, resourceName = resourceName, scheme = UtcpPlain, port = none(int) # Use default port ) return ok(address) except Exception as e: return err[UTCPAddress]("Failed to assign UTCP address: " & e.msg) # UTCP Request/Response Functions proc newUTCPRequest*( address: UTCPAddress, meth: UTCPMethod, payload: JsonNode = newJNull(), headers: Table[string, string] = initTable[string, string]() ): UTCPRequest = ## Create a new UTCP request var actualHeaders = headers if not actualHeaders.hasKey("User-Agent"): actualHeaders["User-Agent"] = UTCP_USER_AGENT if not actualHeaders.hasKey("UTCP-Version"): actualHeaders["UTCP-Version"] = UTCP_VERSION result = UTCPRequest( address: address, meth: meth, headers: actualHeaders, payload: payload, timestamp: now(), requestId: $now().toTime().toUnix() & "-" & $rand(1000000) ) proc newUTCPResponse*( status: UTCPStatus, data: JsonNode = newJNull(), requestId: string = "", headers: Table[string, string] = initTable[string, string]() ): UTCPResponse = ## Create a new UTCP response var actualHeaders = headers if not actualHeaders.hasKey("UTCP-Version"): actualHeaders["UTCP-Version"] = UTCP_VERSION result = UTCPResponse( status: status, headers: actualHeaders, data: data, timestamp: now(), requestId: requestId ) # UTCP Method Handlers # NOTE: Advanced UTCP protocol features (request routing, method handlers) are not yet implemented # They will be added when full UTCP protocol support is needed # For now, the CLI only uses parseUTCPAddress() and formatUTCPAddress() # Utility Functions proc isLocalAddress*(address: UTCPAddress): bool = ## Check if an address refers to the local host let localNames = @["localhost", "127.0.0.1", "::1"] when defined(posix): var buf: array[256, char] if gethostname(cast[cstring](addr buf[0]), 256) == 0: let hostname = $cast[cstring](addr buf[0]) return address.host in localNames or address.host == hostname return address.host in localNames proc getDefaultPort*(scheme: UTCPScheme): int = ## Get the default port for a UTCP scheme case scheme: of UtcpPlain: UTCP_DEFAULT_PORT of UtcpSecure: UTCP_DEFAULT_PORT + 1 # 7778 for secure