@xynehq/jaf
Version:
Juspay Agent Framework - A purely functional agent framework with immutable state and composable tools
233 lines • 8.67 kB
JavaScript
/**
* A2A Task Serialization Functions for JAF
* Pure functions for serializing/deserializing A2A tasks for storage
*/
import { createA2ATaskStorageError, createSuccess, createFailure } from './types.js';
/**
* Pure function to serialize an A2A task for storage
*/
export const serializeA2ATask = (task, metadata) => {
try {
const now = new Date().toISOString();
// Extract status message for indexing if present
let statusMessage;
if (task.status.message) {
try {
statusMessage = JSON.stringify(task.status.message);
}
catch {
// If message serialization fails, continue without it
}
}
const serialized = {
taskId: task.id,
contextId: task.contextId,
state: task.status.state,
taskData: JSON.stringify(task),
statusMessage,
createdAt: task.metadata?.createdAt || now,
updatedAt: now,
metadata: metadata ? JSON.stringify(metadata) : undefined
};
return createSuccess(serialized);
}
catch (error) {
return createFailure(createA2ATaskStorageError('serialize', 'memory', task.id, error));
}
};
/**
* Pure function to deserialize an A2A task from storage
*/
export const deserializeA2ATask = (stored) => {
try {
const task = JSON.parse(stored.taskData);
// Validate that the deserialized task has required fields
if (!task.id || !task.contextId || !task.status || !task.kind) {
return createFailure(createA2ATaskStorageError('deserialize', 'memory', stored.taskId, new Error('Invalid task structure after deserialization')));
}
return createSuccess(task);
}
catch (error) {
return createFailure(createA2ATaskStorageError('deserialize', 'memory', stored.taskId, error));
}
};
/**
* Pure function to create a minimal task representation for indexing
*/
export const createTaskIndex = (task) => {
try {
return createSuccess({
taskId: task.id,
contextId: task.contextId,
state: task.status.state,
timestamp: task.status.timestamp || new Date().toISOString(),
hasHistory: Boolean(task.history && task.history.length > 0),
hasArtifacts: Boolean(task.artifacts && task.artifacts.length > 0)
});
}
catch (error) {
return createFailure(createA2ATaskStorageError('index', 'memory', task.id, error));
}
};
/**
* Pure function to extract searchable text from a task for full-text search
*/
export const extractTaskSearchText = (task) => {
try {
const textParts = [];
// Extract text from status message
if (task.status.message) {
extractTextFromMessage(task.status.message, textParts);
}
// Extract text from history
if (task.history) {
task.history.forEach(message => {
extractTextFromMessage(message, textParts);
});
}
// Extract text from artifacts
if (task.artifacts) {
task.artifacts.forEach(artifact => {
if (artifact.name)
textParts.push(artifact.name);
if (artifact.description)
textParts.push(artifact.description);
artifact.parts.forEach(part => {
if (part.kind === 'text') {
textParts.push(part.text);
}
});
});
}
return createSuccess(textParts.join(' ').trim());
}
catch (error) {
return createFailure(createA2ATaskStorageError('extract-text', 'memory', task.id, error));
}
};
/**
* Helper function to extract text from A2A message parts
*/
const extractTextFromMessage = (message, textParts) => {
message.parts.forEach(part => {
if (part.kind === 'text') {
textParts.push(part.text);
}
else if (part.kind === 'data' && part.data) {
// Extract any string values from data
Object.values(part.data).forEach(value => {
if (typeof value === 'string') {
textParts.push(value);
}
});
}
else if (part.kind === 'file' && part.file.name) {
textParts.push(part.file.name);
}
});
};
/**
* Pure function to validate task data integrity
*/
export const validateTaskIntegrity = (task) => {
try {
// Check required fields
if (!task.id || typeof task.id !== 'string') {
return createFailure(createA2ATaskStorageError('validate', 'memory', task.id, new Error('Task ID is required and must be a string')));
}
if (!task.contextId || typeof task.contextId !== 'string') {
return createFailure(createA2ATaskStorageError('validate', 'memory', task.id, new Error('Context ID is required and must be a string')));
}
if (!task.status || !task.status.state) {
return createFailure(createA2ATaskStorageError('validate', 'memory', task.id, new Error('Task status and state are required')));
}
if (task.kind !== 'task') {
return createFailure(createA2ATaskStorageError('validate', 'memory', task.id, new Error('Task kind must be "task"')));
}
// Validate history if present
if (task.history) {
if (!Array.isArray(task.history)) {
return createFailure(createA2ATaskStorageError('validate', 'memory', task.id, new Error('Task history must be an array')));
}
for (const message of task.history) {
if (!message.messageId || !message.parts || !Array.isArray(message.parts)) {
return createFailure(createA2ATaskStorageError('validate', 'memory', task.id, new Error('Invalid message in task history')));
}
}
}
// Validate artifacts if present
if (task.artifacts) {
if (!Array.isArray(task.artifacts)) {
return createFailure(createA2ATaskStorageError('validate', 'memory', task.id, new Error('Task artifacts must be an array')));
}
for (const artifact of task.artifacts) {
if (!artifact.artifactId || !artifact.parts || !Array.isArray(artifact.parts)) {
return createFailure(createA2ATaskStorageError('validate', 'memory', task.id, new Error('Invalid artifact in task')));
}
}
}
return createSuccess(true);
}
catch (error) {
return createFailure(createA2ATaskStorageError('validate', 'memory', task.id, error));
}
};
/**
* Pure function to create a deep copy of a task (for immutability)
*/
export const cloneTask = (task) => {
try {
// Use JSON serialization for deep cloning
const cloned = JSON.parse(JSON.stringify(task));
return createSuccess(cloned);
}
catch (error) {
return createFailure(createA2ATaskStorageError('clone', 'memory', task.id, error));
}
};
/**
* Pure function to sanitize task data for storage
* Removes any potentially dangerous or invalid data
*/
export const sanitizeTask = (task) => {
try {
const validationResult = validateTaskIntegrity(task);
if (!validationResult.success) {
return validationResult;
}
// Clone the task to avoid mutation
const cloneResult = cloneTask(task);
if (!cloneResult.success) {
return cloneResult;
}
let sanitized = cloneResult.data;
// Ensure timestamps are valid ISO strings
if (sanitized.status.timestamp) {
try {
const date = new Date(sanitized.status.timestamp);
sanitized = {
...sanitized,
status: {
...sanitized.status,
timestamp: date.toISOString()
}
};
}
catch {
// Remove invalid timestamp
sanitized = {
...sanitized,
status: {
...sanitized.status,
timestamp: undefined
}
};
}
}
return createSuccess(sanitized);
}
catch (error) {
return createFailure(createA2ATaskStorageError('sanitize', 'memory', task.id, error));
}
};
//# sourceMappingURL=serialization.js.map