UNPKG

@openfloor/protocol

Version:

Open Floor Protocol implementation for JavaScript/TypeScript - enables interoperable multi-agent conversations

278 lines 8.74 kB
/** * @fileoverview Utility functions for the Open Floor Protocol * @author Open Voice Interoperability Initiative * @version 0.0.1 * @license Apache-2.0 */ /** * Parses an ISO 8601 duration string and returns the number of milliseconds * Supports the format: P[n]D[T[n]H[n]M[n]S] * * @param duration - ISO 8601 duration string (e.g., "PT3H30M15S") * @returns Duration in milliseconds * @throws Error if the duration string is invalid * * @example * ```typescript * parseIsoDuration("PT1H30M"); // Returns 5400000 (1.5 hours in ms) * parseIsoDuration("P1DT2H"); // Returns 93600000 (26 hours in ms) * ``` */ export function parseIsoDuration(duration) { const regex = /^P(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?)?$/; const matches = duration.match(regex); if (!matches) { throw new Error(`Invalid ISO 8601 duration: ${duration}`); } const days = parseInt(matches[1] || '0', 10); const hours = parseInt(matches[2] || '0', 10); const minutes = parseInt(matches[3] || '0', 10); const seconds = parseFloat(matches[4] || '0'); return ((days * 24 + hours) * 60 + minutes) * 60000 + seconds * 1000; } /** * Converts milliseconds to ISO 8601 duration format * * @param milliseconds - Duration in milliseconds * @returns ISO 8601 duration string * * @example * ```typescript * millisecondsToIsoDuration(5400000); // Returns "PT1H30M" * millisecondsToIsoDuration(90000); // Returns "PT1M30S" * ``` */ export function millisecondsToIsoDuration(milliseconds) { const totalSeconds = Math.floor(milliseconds / 1000); const hours = Math.floor(totalSeconds / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; const parts = []; if (hours > 0) parts.push(`${hours}H`); if (minutes > 0) parts.push(`${minutes}M`); if (seconds > 0 || parts.length === 0) parts.push(`${seconds}S`); return `PT${parts.join('')}`; } /** * Generates a UUID v4 string using the available crypto API * Falls back to a simple implementation if crypto.randomUUID is not available * * @returns A UUID v4 string * * @example * ```typescript * const id = generateUUID(); // "f47ac10b-58cc-4372-a567-0e02b2c3d479" * ``` */ export function generateUUID() { // Use native crypto.randomUUID if available (Node 16.7+ / modern browsers) if (typeof crypto !== 'undefined' && crypto.randomUUID) { return crypto.randomUUID(); } // Fallback implementation return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { const r = Math.random() * 16 | 0; const v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } /** * Resolves JSON Path expressions with support for the substring() extension * * @param path - JSON Path expression (e.g., "$.features.text.tokens[0].value") * @param data - Object to query * @returns Array of matched values * * @example * ```typescript * const data = { features: { text: { tokens: [{ value: "hello world" }] } } }; * resolveJsonPath("$.features.text.tokens[0].value", data); // ["hello world"] * resolveJsonPath("$.features.text.tokens[0].value.substring(0,5)", data); // ["hello"] * ``` */ export function resolveJsonPath(path, data) { // Check for substring() extension const substringMatch = path.match(/^(.+)\.substring\((\d+),(\d+)\)$/); if (substringMatch) { const [, basePath, startStr, endStr] = substringMatch; if (startStr === undefined || endStr === undefined || basePath === undefined) { return []; } const start = parseInt(startStr, 10); const end = parseInt(endStr, 10); const baseResults = resolveBasicJsonPath(basePath, data); return baseResults .filter((result) => typeof result === 'string') .map(str => str.substring(start, end)); } return resolveBasicJsonPath(path, data); } /** * Basic JSON Path resolution without extensions * Supports: $, ., [], array indexing, property access * * @param path - JSON Path expression * @param data - Object to query * @returns Array of matched values */ function resolveBasicJsonPath(path, data) { if (!path.startsWith('$')) { throw new Error('JSON Path must start with $'); } const parts = path.slice(1).split(/[.\[]/).filter(Boolean); let current = data; for (const part of parts) { if (part.endsWith(']')) { // Array index access const index = parseInt(part.slice(0, -1), 10); if (Array.isArray(current)) { current = current[index]; } else { return []; } } else { // Property access if (current && typeof current === 'object' && part in current) { current = current[part]; } else { return []; } } if (current === undefined) { return []; } } return [current]; } /** * Validates that a string is a properly formatted URI * * @param uri - String to validate * @returns True if the string is a valid URI format * * @example * ```typescript * isValidUri("tag:example.com,2025:agent1"); // true * isValidUri("https://example.com/agent"); // true * isValidUri("not-a-uri"); // false * ``` */ export function isValidUri(uri) { try { // Basic URI validation - must have scheme:path format const colonIndex = uri.indexOf(':'); if (colonIndex === -1 || colonIndex === 0) { return false; } const scheme = uri.substring(0, colonIndex); const path = uri.substring(colonIndex + 1); // Scheme must be alphanumeric + some special chars if (!/^[a-zA-Z][a-zA-Z0-9+.-]*$/.test(scheme)) { return false; } // Path must not be empty if (!path) { return false; } return true; } catch { return false; } } /** * Validates that a string is a properly formatted URL * * @param url - String to validate * @returns True if the string is a valid URL * * @example * ```typescript * isValidUrl("https://example.com/api"); // true * isValidUrl("http://localhost:3000"); // true * isValidUrl("not-a-url"); // false * ``` */ export function isValidUrl(url) { try { new URL(url); return true; } catch { return false; } } /** * Validates that a confidence value is within the valid range (0.0 to 1.0) * * @param confidence - Confidence value to validate * @returns True if the confidence is valid * * @example * ```typescript * isValidConfidence(0.95); // true * isValidConfidence(1.0); // true * isValidConfidence(1.5); // false * ``` */ export function isValidConfidence(confidence) { return typeof confidence === 'number' && confidence >= 0 && confidence <= 1 && !Number.isNaN(confidence); } /** * Validates encoding string against allowed values * * @param encoding - Encoding string to validate * @returns True if encoding is valid */ export function isValidEncoding(encoding) { const validEncodings = ['ISO-8859-1', 'iso-8859-1', 'UTF-8', 'utf-8']; return validEncodings.includes(encoding); } /** * Deep clones an object using structuredClone if available, otherwise JSON fallback * * @param obj - Object to clone * @returns Deep clone of the object */ export function deepClone(obj) { if (typeof structuredClone !== 'undefined') { return structuredClone(obj); } // Fallback for environments without structuredClone return JSON.parse(JSON.stringify(obj)); } /** * Checks if an object has all required properties * * @param obj - Object to check * @param requiredProps - Array of required property names * @returns True if all required properties are present and not undefined */ export function hasRequiredProperties(obj, requiredProps) { if (!obj || typeof obj !== 'object') { return false; } const record = obj; return requiredProps.every(prop => prop in record && record[prop] !== undefined); } /** * Creates a standardized error message for validation failures * * @param fieldName - Name of the field that failed validation * @param value - The invalid value * @param requirement - Description of what was required * @returns Formatted error message */ export function createValidationError(fieldName, value, requirement) { return `${fieldName}: expected ${requirement}, got ${typeof value === 'string' ? `"${value}"` : typeof value}`; } //# sourceMappingURL=utils.js.map