Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package cc.unitmesh.agent.artifact

/**
* Android implementation for time utilities
*/
actual fun getCurrentTimeMillis(): Long = System.currentTimeMillis()
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,12 @@ enum class AgentType {
/**
* Web edit mode - browse, select DOM elements, and interact with web pages
*/
WEB_EDIT;
WEB_EDIT,

/**
* Artifact Unit mode - create reversible executable artifacts from AI-generated code
*/
ARTIFACT_UNIT;

fun getDisplayName(): String = when (this) {
LOCAL_CHAT -> "Chat"
Expand All @@ -55,6 +60,7 @@ enum class AgentType {
CHAT_DB -> "ChatDB"
REMOTE -> "Remote"
WEB_EDIT -> "WebEdit"
ARTIFACT_UNIT -> "Unit"
}

companion object {
Expand All @@ -67,6 +73,7 @@ enum class AgentType {
"documentreader", "documents" -> KNOWLEDGE
"chatdb", "database" -> CHAT_DB
"webedit", "web" -> WEB_EDIT
"artifactunit", "artifact", "unit" -> ARTIFACT_UNIT
else -> LOCAL_CHAT
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package cc.unitmesh.agent.artifact

import cc.unitmesh.agent.model.AgentContext
import cc.unitmesh.llm.LLMMessage

/**
* Artifact Agent - specialized agent for creating reversible executable artifacts
*
* This agent combines code generation capabilities with artifact building
* to produce standalone executables that contain their own source code
* and generation history.
*/
interface ArtifactAgent {
/**
* Generate code and build an artifact from a user prompt
*
* @param prompt The user's request
* @param context The agent context
* @param type The type of artifact to build
* @return Result of the artifact generation
*/
suspend fun generateArtifact(
prompt: String,
context: AgentContext,
type: ArtifactType = ArtifactType.PYTHON_SCRIPT
): ArtifactGenerationResult

/**
* Restore a chat session from an extracted artifact
*
* @param payload The extracted artifact payload
* @return Restored messages for the chat interface
*/
fun restoreChatFromArtifact(payload: ArtifactPayload): List<LLMMessage>

/**
* Update an existing artifact with new code changes
*
* @param originalPayload The original artifact payload
* @param newCode The updated code
* @param updateMessage Description of the update
* @return Result of the artifact update
*/
suspend fun updateArtifact(
originalPayload: ArtifactPayload,
newCode: Map<String, String>,
updateMessage: String
): ArtifactGenerationResult
}

/**
* Result of artifact generation
*/
sealed class ArtifactGenerationResult {
/**
* Generation successful
*/
data class Success(
val payload: ArtifactPayload,
val generatedCode: String
) : ArtifactGenerationResult()

/**
* Generation failed
*/
data class Error(
val message: String,
val cause: Throwable? = null
) : ArtifactGenerationResult()

/**
* Generation in progress
*/
data class Progress(
val stage: GenerationStage,
val progress: Float,
val message: String
) : ArtifactGenerationResult()
}

/**
* Generation stages
*/
enum class GenerationStage {
ANALYZING_PROMPT,
GENERATING_CODE,
DETECTING_DEPENDENCIES,
CREATING_PAYLOAD,
COMPLETE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package cc.unitmesh.agent.artifact

/**
* Binary format constants for artifact packaging
*
* Defines the structure of the binary artifact file:
* [Runtime Shell] + [MAGIC_DELIMITER] + [Payload ZIP] + [Metadata JSON] + [Footer]
*/
object ArtifactBinaryFormat {
/**
* Magic delimiter to separate runtime shell from payload
* This is a unique byte sequence unlikely to appear in normal executables
*/
const val MAGIC_DELIMITER = "@@AUTODEV_ARTIFACT_DATA@@"

/**
* Version of the binary format
*/
const val FORMAT_VERSION = "1.0.0"

/**
* Maximum size of metadata JSON (to prevent reading entire file)
*/
const val MAX_METADATA_SIZE = 10 * 1024 * 1024 // 10 MB

/**
* Maximum size of payload ZIP
*/
const val MAX_PAYLOAD_SIZE = 100 * 1024 * 1024 // 100 MB

/**
* Footer size (contains offsets and checksums)
*/
const val FOOTER_SIZE = 128

/**
* Checksum algorithm
*/
const val CHECKSUM_ALGORITHM = "SHA-256"
}

/**
* Footer structure for the artifact binary
*
* Located at the end of the file, contains pointers to locate the data sections
*/
data class ArtifactFooter(
/**
* Format version
*/
val formatVersion: String,

/**
* Offset of the magic delimiter from start of file
*/
val delimiterOffset: Long,

/**
* Offset of the payload ZIP from start of file
*/
val payloadOffset: Long,

/**
* Size of the payload ZIP in bytes
*/
val payloadSize: Long,

/**
* Offset of the metadata JSON from start of file
*/
val metadataOffset: Long,

/**
* Size of the metadata JSON in bytes
*/
val metadataSize: Long,

/**
* SHA-256 checksum of the payload
*/
val payloadChecksum: String,

/**
* SHA-256 checksum of the metadata
*/
val metadataChecksum: String
) {
/**
* Serialize footer to byte array
*/
fun toBytes(): ByteArray {
// Format: version(16) + delimiterOffset(8) + payloadOffset(8) + payloadSize(8) +
// metadataOffset(8) + metadataSize(8) + payloadChecksum(64) + metadataChecksum(64)
val buffer = StringBuilder()
buffer.append(formatVersion.padEnd(16, '\u0000'))
buffer.append(delimiterOffset.toString().padEnd(8, '0'))
buffer.append(payloadOffset.toString().padEnd(8, '0'))
buffer.append(payloadSize.toString().padEnd(8, '0'))
buffer.append(metadataOffset.toString().padEnd(8, '0'))
buffer.append(metadataSize.toString().padEnd(8, '0'))
buffer.append(payloadChecksum.padEnd(64, '0'))
buffer.append(metadataChecksum.padEnd(64, '0'))

return buffer.toString().encodeToByteArray()
}

companion object {
/**
* Parse footer from byte array
*/
fun fromBytes(bytes: ByteArray): ArtifactFooter {
require(bytes.size >= ArtifactBinaryFormat.FOOTER_SIZE) {
"Invalid footer size: ${bytes.size}"
}

val str = bytes.decodeToString()
return ArtifactFooter(
formatVersion = str.substring(0, 16).trimEnd('\u0000'),
delimiterOffset = str.substring(16, 24).toLong(),
payloadOffset = str.substring(24, 32).toLong(),
payloadSize = str.substring(32, 40).toLong(),
metadataOffset = str.substring(40, 48).toLong(),
metadataSize = str.substring(48, 56).toLong(),
payloadChecksum = str.substring(56, 120).trimEnd('0'),
metadataChecksum = str.substring(120, 184).trimEnd('0')
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package cc.unitmesh.agent.artifact

/**
* Interface for building executable artifacts from AI-generated code
*
* This is a platform-independent interface. Platform-specific implementations
* handle the actual binary operations (file I/O, compression, etc.).
*/
interface ArtifactBuilder {
/**
* Build an artifact from a payload
*
* @param payload The artifact payload containing source code and metadata
* @param shellTemplate Path to the runtime shell template (pre-built executable)
* @return Result containing the artifact binary data or error
*/
suspend fun build(payload: ArtifactPayload, shellTemplate: String): ArtifactBuildResult

/**
* Validate a payload before building
*
* @param payload The payload to validate
* @return Validation result with any errors or warnings
*/
fun validate(payload: ArtifactPayload): ValidationResult

/**
* Get available runtime shell templates for a given artifact type
*
* @param type The artifact type
* @return List of available shell templates
*/
suspend fun getAvailableShells(type: ArtifactType): List<ShellTemplate>
}

/**
* Result of an artifact build operation
*/
sealed class ArtifactBuildResult {
/**
* Successful build with binary data
*/
data class Success(
val binaryData: ByteArray,
val fileName: String,
val metadata: ArtifactMetadata
) : ArtifactBuildResult() {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false

other as Success

if (!binaryData.contentEquals(other.binaryData)) return false
if (fileName != other.fileName) return false
if (metadata != other.metadata) return false

return true
}

override fun hashCode(): Int {
var result = binaryData.contentHashCode()
result = 31 * result + fileName.hashCode()
result = 31 * result + metadata.hashCode()
return result
}
}

/**
* Build failed with error
*/
data class Error(
val message: String,
val cause: Throwable? = null
) : ArtifactBuildResult()

/**
* Build in progress with status update
*/
data class Progress(
val stage: BuildStage,
val progress: Float, // 0.0 to 1.0
val message: String
) : ArtifactBuildResult()
}

/**
* Build stages for progress reporting
*/
enum class BuildStage {
VALIDATING,
LOADING_SHELL,
COMPRESSING_PAYLOAD,
SERIALIZING_METADATA,
INJECTING_PAYLOAD,
WRITING_FOOTER,
FINALIZING,
COMPLETE
}

/**
* Validation result
*/
data class ValidationResult(
val isValid: Boolean,
val errors: List<String> = emptyList(),
val warnings: List<String> = emptyList()
) {
companion object {
fun success() = ValidationResult(true)
fun error(vararg errors: String) = ValidationResult(false, errors.toList())
fun withWarnings(vararg warnings: String) = ValidationResult(true, emptyList(), warnings.toList())
}
}

/**
* Runtime shell template information
*/
data class ShellTemplate(
val id: String,
val name: String,
val type: ArtifactType,
val platform: Platform,
val version: String,
val path: String,
val description: String = ""
) {
enum class Platform {
WINDOWS_X64,
WINDOWS_ARM64,
LINUX_X64,
LINUX_ARM64,
MACOS_X64,
MACOS_ARM64,
UNIVERSAL
}
}
Loading
Loading