import { randomUUID } from 'node:crypto'; import type { IncomingMessage, ServerResponse } from 'node:http'; import type { HostApiContext } from '../context'; import { sendJson } from '../route-utils'; import { WORKS_SQUARE_CONFIG } from '../works-config'; import { getValidWorksSquareAccessToken } from '../../services/works-square-session'; import { proxyAwareFetch } from '../../utils/proxy-fetch'; const MAX_REQUEST_BYTES = 64 * 1024; const MAX_RESPONSE_BYTES = 256 * 1024; const DEFAULT_TIMEOUT_MS = 15_000; const LOCAL_ROOT = '/api/works/ai-hardware'; const UPSTREAM_ROOT = '/api/ai-hardware'; const REVISION_ETAG = /^(?:W\/)?"(0|[1-9]\d*)"$/; const LOCAL_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,35}$/; const OPERATION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; const MAX_RETRY_AFTER_SECONDS = 2; type TokenGetter = typeof getValidWorksSquareAccessToken; export type AiHardwareRouteDependencies = { fetchImpl?: typeof fetch; getAccessToken?: TokenGetter; apiBaseUrl?: string; randomUuid?: () => string; timeoutMs?: number; }; class SafeRouteError extends Error { constructor( readonly status: number, readonly code: string, message: string, readonly retryable = false, ) { super(message); } } function isRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value); } function asRequiredString(value: unknown): string | null { return typeof value === 'string' && value.trim() ? value.trim() : null; } function asRevision(value: unknown): number | null { return Number.isSafeInteger(value) && (value as number) >= 0 ? value as number : null; } function projectAgent(value: unknown): Record | null { if (!isRecord(value)) return null; const id = asRequiredString(value.id); const name = asRequiredString(value.name); const configRevision = asRevision(value.config_revision); return id && name && configRevision !== null ? { id, name, config_revision: configRevision } : null; } function projectDevice(value: unknown): Record | null { if (!isRecord(value)) return null; const id = asRequiredString(value.id); const agentId = asRequiredString(value.agent_id); const assignmentRevision = asRevision(value.assignment_revision); return id && agentId && assignmentRevision !== null ? { id, agent_id: agentId, assignment_revision: assignmentRevision } : null; } const CONFIG_STRING_FIELDS = [ 'system_prompt', 'lang_code', 'language', 'asr_model_id', 'vad_model_id', 'llm_model_id', 'slm_model_id', 'vllm_model_id', 'tts_model_id', 'tts_voice_id', 'tts_language', 'mem_model_id', 'intent_model_id', ] as const; const CONFIG_NUMBER_FIELDS = ['tts_volume', 'tts_rate', 'tts_pitch'] as const; const CONFIG_STRING_LIMITS: Record = { system_prompt: 16000, lang_code: 10, language: 10, asr_model_id: 32, vad_model_id: 64, llm_model_id: 32, slm_model_id: 255, vllm_model_id: 32, tts_model_id: 32, tts_voice_id: 32, tts_language: 50, mem_model_id: 32, intent_model_id: 32, }; const CLEARABLE_CONFIG_FIELDS = new Set([ ...CONFIG_STRING_FIELDS, ...CONFIG_NUMBER_FIELDS, ]); const CONFIG_UPDATE_FIELDS = new Set([ 'agent_name', ...CONFIG_STRING_FIELDS, ...CONFIG_NUMBER_FIELDS, 'chat_history_conf', 'clear_fields', ]); function projectAgentConfig(value: unknown): Record | null { const agent = projectAgent(value); if (!agent || !isRecord(value)) return null; const result: Record = { ...agent }; for (const field of CONFIG_STRING_FIELDS) { const fieldValue = value[field]; if (fieldValue !== null && typeof fieldValue !== 'string') return null; result[field] = fieldValue; } for (const field of CONFIG_NUMBER_FIELDS) { const fieldValue = value[field]; if (fieldValue !== null && (!Number.isInteger(fieldValue) || (fieldValue as number) < -100 || (fieldValue as number) > 100)) return null; result[field] = fieldValue; } const chatHistory = value.chat_history_conf; if (chatHistory !== null && (!Number.isInteger(chatHistory) || (chatHistory as number) < 0 || (chatHistory as number) > 2)) return null; result.chat_history_conf = chatHistory; return result; } function projectOverview(value: unknown): Record | null { if (!isRecord(value) || !Array.isArray(value.agents) || !Array.isArray(value.devices)) return null; const allowedStatuses = new Set([ 'unprovisioned', 'provisioning', 'active', 'credential_recovery_required', 'invalid', ]); if (typeof value.status !== 'string' || !allowedStatuses.has(value.status)) return null; const agents = value.agents.map(projectAgent); const devices = value.devices.map(projectDevice); if (agents.some((item) => item === null) || devices.some((item) => item === null)) return null; return { status: value.status, agents, devices }; } async function readBoundedJson(req: IncomingMessage): Promise> { const declared = Number(req.headers['content-length']); if (Number.isFinite(declared) && declared > MAX_REQUEST_BYTES) { throw new SafeRouteError(413, 'AI_HARDWARE_REQUEST_TOO_LARGE', 'AI hardware request is too large'); } const chunks: Buffer[] = []; let size = 0; for await (const chunk of req) { const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); size += buffer.byteLength; if (size > MAX_REQUEST_BYTES) { throw new SafeRouteError(413, 'AI_HARDWARE_REQUEST_TOO_LARGE', 'AI hardware request is too large'); } chunks.push(buffer); } try { const value = JSON.parse(Buffer.concat(chunks).toString('utf8')) as unknown; if (!isRecord(value)) throw new Error('not an object'); return value; } catch { throw new SafeRouteError(400, 'AI_HARDWARE_INVALID_REQUEST', 'Invalid AI hardware request'); } } async function readBoundedResponse(response: Response): Promise { const declared = Number(response.headers.get('content-length')); if (Number.isFinite(declared) && declared > MAX_RESPONSE_BYTES) { await response.body?.cancel().catch(() => undefined); throw new SafeRouteError(502, 'AI_HARDWARE_RESPONSE_TOO_LARGE', 'AI hardware service returned too much data', true); } if (!response.body) return null; const reader = response.body.getReader(); const chunks: Uint8Array[] = []; let size = 0; while (true) { const { done, value } = await reader.read(); if (done) break; size += value.byteLength; if (size > MAX_RESPONSE_BYTES) { await reader.cancel().catch(() => undefined); throw new SafeRouteError(502, 'AI_HARDWARE_RESPONSE_TOO_LARGE', 'AI hardware service returned too much data', true); } chunks.push(value); } const text = Buffer.concat(chunks.map((chunk) => Buffer.from(chunk))).toString('utf8').trim(); if (!text) return null; try { return JSON.parse(text) as unknown; } catch { throw new SafeRouteError(502, 'AI_HARDWARE_INVALID_RESPONSE', 'AI hardware service returned an invalid response', true); } } type TimedResponse = { response: Response; signal: AbortSignal; finish: () => void; }; async function readTimedResponse(call: TimedResponse): Promise { try { return await readBoundedResponse(call.response); } catch (error) { if (call.signal.aborted) { throw new SafeRouteError(504, 'AI_HARDWARE_TIMEOUT', 'AI hardware service timed out', true); } throw error; } finally { call.finish(); } } async function readSafeErrorPayload(call: TimedResponse): Promise { try { return await readTimedResponse(call); } catch (error) { if (error instanceof SafeRouteError && error.code === 'AI_HARDWARE_TIMEOUT') throw error; return null; } } async function cancelTimedResponse(call: TimedResponse): Promise { try { await call.response.body?.cancel().catch(() => undefined); } finally { call.finish(); } } function ensureExactKeys(body: Record, allowed: Set): void { if (Object.keys(body).some((key) => !allowed.has(key))) { throw new SafeRouteError(400, 'AI_HARDWARE_INVALID_REQUEST', 'Invalid AI hardware request'); } } function requireLocalId(value: unknown): string { const id = asRequiredString(value); if (!id || !LOCAL_ID.test(id)) { throw new SafeRouteError(400, 'AI_HARDWARE_INVALID_REQUEST', 'Invalid AI hardware request'); } return id; } function decodeLocalId(value: string): string { try { return requireLocalId(decodeURIComponent(value)); } catch (error) { if (error instanceof SafeRouteError) throw error; throw new SafeRouteError(400, 'AI_HARDWARE_INVALID_REQUEST', 'Invalid AI hardware request'); } } function requireRevision(body: Record): number { const revision = asRevision(body.revision); if (revision === null) { throw new SafeRouteError(400, 'AI_HARDWARE_INVALID_REQUEST', 'Invalid AI hardware request'); } return revision; } function takeOperationId(body: Record, createUuid: () => string): string { const candidate = body.client_operation_id ?? createUuid(); if (typeof candidate !== 'string' || !OPERATION_ID.test(candidate)) { throw new SafeRouteError(400, 'AI_HARDWARE_INVALID_REQUEST', 'Invalid AI hardware request'); } delete body.client_operation_id; return candidate.toLowerCase(); } function validateConfigUpdate(body: Record): Record { ensureExactKeys(body, new Set(['revision', ...CONFIG_UPDATE_FIELDS])); requireRevision(body); const output: Record = {}; for (const [key, value] of Object.entries(body)) { if (key === 'revision') continue; if (key === 'agent_name') { const name = asRequiredString(value); if (!name || name.length > 64) throw new SafeRouteError(400, 'AI_HARDWARE_INVALID_REQUEST', 'Invalid AI hardware request'); output[key] = name; } else if (key === 'clear_fields') { if (!Array.isArray(value) || value.length > 16 || new Set(value).size !== value.length || value.some((item) => typeof item !== 'string' || !CLEARABLE_CONFIG_FIELDS.has(item))) { throw new SafeRouteError(400, 'AI_HARDWARE_INVALID_REQUEST', 'Invalid AI hardware request'); } output[key] = value; } else if (CONFIG_NUMBER_FIELDS.includes(key as typeof CONFIG_NUMBER_FIELDS[number])) { if (!Number.isInteger(value) || (value as number) < -100 || (value as number) > 100) throw new SafeRouteError(400, 'AI_HARDWARE_INVALID_REQUEST', 'Invalid AI hardware request'); output[key] = value; } else if (key === 'chat_history_conf') { if (!Number.isInteger(value) || (value as number) < 0 || (value as number) > 2) throw new SafeRouteError(400, 'AI_HARDWARE_INVALID_REQUEST', 'Invalid AI hardware request'); output[key] = value; } else { const limit = CONFIG_STRING_LIMITS[key]; if (typeof value !== 'string' || !value.trim() || limit === undefined || value.length > limit) throw new SafeRouteError(400, 'AI_HARDWARE_INVALID_REQUEST', 'Invalid AI hardware request'); output[key] = value; } } const clears = Array.isArray(output.clear_fields) ? new Set(output.clear_fields) : new Set(); if (Object.keys(output).some((key) => key !== 'clear_fields' && clears.has(key))) { throw new SafeRouteError(400, 'AI_HARDWARE_INVALID_REQUEST', 'Invalid AI hardware request'); } if (!Object.keys(output).length || (Object.keys(output).length === 1 && Array.isArray(output.clear_fields) && output.clear_fields.length === 0)) { throw new SafeRouteError(400, 'AI_HARDWARE_INVALID_REQUEST', 'Invalid AI hardware request'); } return output; } function revisionFromEtag(response: Response): number { const etag = response.headers.get('etag'); const match = etag?.match(REVISION_ETAG); if (!match) throw new SafeRouteError(502, 'AI_HARDWARE_INVALID_ETAG', 'AI hardware service returned an invalid revision', true); const revision = Number(match[1]); if (!Number.isSafeInteger(revision)) throw new SafeRouteError(502, 'AI_HARDWARE_INVALID_ETAG', 'AI hardware service returned an invalid revision', true); return revision; } function isJsonContentType(response: Response): boolean { const contentType = response.headers.get('content-type')?.toLowerCase().split(';', 1)[0].trim(); return contentType === 'application/json' || contentType === 'application/problem+json'; } function safeErrorForStatus(status: number): { code: string; error: string; retryable: boolean } { if (status === 401) return { code: 'AI_HARDWARE_AUTH_REQUIRED', error: 'Works Square sign-in is required', retryable: false }; if (status === 403) return { code: 'AI_HARDWARE_FORBIDDEN', error: 'AI hardware operation is not allowed', retryable: false }; if (status === 404) return { code: 'AI_HARDWARE_RESOURCE_NOT_FOUND', error: 'AI hardware resource was not found', retryable: false }; if (status === 409 || status === 412) return { code: 'AI_HARDWARE_REVISION_CONFLICT', error: 'AI hardware data changed; refresh and retry', retryable: false }; if (status === 429) return { code: 'AI_HARDWARE_RATE_LIMITED', error: 'AI hardware service is busy; retry later', retryable: true }; if (status >= 500) return { code: 'AI_HARDWARE_UNAVAILABLE', error: 'AI hardware service is temporarily unavailable', retryable: true }; return { code: 'AI_HARDWARE_REQUEST_FAILED', error: 'AI hardware request was rejected', retryable: false }; } const SAFE_UPSTREAM_ERRORS: Record = { ai_hardware_idempotency_conflict: 'AI hardware request conflicts with an earlier operation', ai_hardware_operation_in_progress: 'AI hardware operation is still in progress', ai_hardware_device_already_bound: 'AI hardware device is already bound', ai_hardware_revision_conflict: 'AI hardware data changed; refresh and retry', ai_hardware_state_conflict: 'AI hardware operation conflicts with the current state', ai_hardware_revision_required: 'A current AI hardware revision is required', ai_hardware_activation_code_invalid: 'AI hardware activation code is invalid', ai_hardware_request_rejected: 'AI hardware request was rejected', ai_hardware_resource_not_found: 'AI hardware resource was not found', ai_hardware_provider_state_conflict: 'AI hardware provider state is inconsistent', ai_hardware_credential_recovery_required: 'AI hardware credential recovery is required', ai_hardware_credential_unavailable: 'AI hardware credential is unavailable', ai_hardware_credential_recovery_unavailable: 'AI hardware credential recovery is not currently available', ai_hardware_unconfigured: 'AI hardware integration is not configured', ai_hardware_disabled: 'AI hardware module is not enabled', ai_hardware_idempotency_key_invalid: 'AI hardware operation identity is invalid', ai_hardware_revision_invalid: 'AI hardware revision is invalid', xiaozhi_hardware_protocol_error: 'AI hardware service returned an invalid response', xiaozhi_hardware_timeout: 'AI hardware service timed out', xiaozhi_hardware_unavailable: 'AI hardware service is temporarily unavailable', }; function safeUpstreamError(payload: unknown, status: number): { code: string; error: string; retryable: boolean } { const detail = isRecord(payload) && isRecord(payload.detail) ? payload.detail : null; const code = detail && typeof detail.error_code === 'string' && SAFE_UPSTREAM_ERRORS[detail.error_code] ? detail.error_code : null; if (!code) return safeErrorForStatus(status); return { code, error: SAFE_UPSTREAM_ERRORS[code], retryable: detail?.retryable === true }; } function sendFailure( res: ServerResponse, localStatus: number, status: number, code: string, error: string, retryable: boolean, retryAfterSeconds?: number, operationId?: string, ): void { sendJson(res, localStatus, { success: false, status, code, error, retryable, ...(retryAfterSeconds === undefined ? {} : { retry_after_seconds: retryAfterSeconds }), ...(operationId === undefined ? {} : { operation_id: operationId }), }); } function retryAfterSeconds(response: Response, cap = 86_400): number | undefined { const raw = response.headers.get('retry-after')?.trim(); if (!raw) return undefined; const delta = /^\d+$/.test(raw) ? Number(raw) : Math.ceil((Date.parse(raw) - Date.now()) / 1000); if (!Number.isFinite(delta) || delta < 0) return undefined; return Math.min(delta, cap); } export function createAiHardwareRouteHandler(dependencies: AiHardwareRouteDependencies = {}) { const fetchImpl = dependencies.fetchImpl ?? proxyAwareFetch; const getAccessToken = dependencies.getAccessToken ?? getValidWorksSquareAccessToken; const apiBaseUrl = (dependencies.apiBaseUrl ?? WORKS_SQUARE_CONFIG.apiBaseUrl).replace(/\/+$/, ''); const createUuid = dependencies.randomUuid ?? randomUUID; const timeoutMs = dependencies.timeoutMs ?? DEFAULT_TIMEOUT_MS; return async function handleAiHardwareRoutes( req: IncomingMessage, res: ServerResponse, url: URL, _ctx: HostApiContext, ): Promise { if (url.pathname !== LOCAL_ROOT && !url.pathname.startsWith(`${LOCAL_ROOT}/`)) return false; let operationId: string | undefined; try { let upstreamPath: string; let body: Record | undefined; let project: (value: unknown) => Record | null; let requireEtag = false; let ifMatch: number | undefined; let expectedStatus: number; const method = req.method ?? 'GET'; if (method === 'GET' && url.pathname === LOCAL_ROOT) { upstreamPath = UPSTREAM_ROOT; project = projectOverview; expectedStatus = 200; } else if (method === 'POST' && url.pathname === `${LOCAL_ROOT}/credential-recovery`) { const input = await readBoundedJson(req); ensureExactKeys(input, new Set(['client_operation_id'])); operationId = takeOperationId(input, createUuid); upstreamPath = `${UPSTREAM_ROOT}/credential-recovery`; project = projectOverview; expectedStatus = 200; } else if (method === 'POST' && url.pathname === `${LOCAL_ROOT}/agents`) { const input = await readBoundedJson(req); ensureExactKeys(input, new Set(['agent_name', 'client_operation_id'])); operationId = takeOperationId(input, createUuid); const agentName = asRequiredString(input.agent_name); if (!agentName || agentName.length > 64) throw new SafeRouteError(400, 'AI_HARDWARE_INVALID_REQUEST', 'Invalid AI hardware request'); upstreamPath = `${UPSTREAM_ROOT}/agents`; body = { agent_name: agentName }; project = projectAgent; expectedStatus = 201; } else if (method === 'POST' && url.pathname === `${LOCAL_ROOT}/device-bindings`) { const input = await readBoundedJson(req); ensureExactKeys(input, new Set(['activation_code', 'agent_id', 'client_operation_id'])); operationId = takeOperationId(input, createUuid); if (typeof input.activation_code !== 'string' || !/^\d{6}$/.test(input.activation_code)) { throw new SafeRouteError(400, 'AI_HARDWARE_INVALID_REQUEST', 'Invalid AI hardware request'); } upstreamPath = `${UPSTREAM_ROOT}/device-bindings`; body = { activation_code: input.activation_code, agent_id: requireLocalId(input.agent_id) }; project = projectDevice; expectedStatus = 201; } else { const agentMatch = url.pathname.match(/^\/api\/works\/ai-hardware\/agents\/([^/]+)$/); const assignmentMatch = url.pathname.match(/^\/api\/works\/ai-hardware\/devices\/([^/]+)\/agent-assignment$/); if (agentMatch && (method === 'GET' || method === 'PATCH')) { const id = decodeLocalId(agentMatch[1]); upstreamPath = `/api/ai-hardware/agents/${encodeURIComponent(id)}`; project = projectAgentConfig; requireEtag = true; expectedStatus = 200; if (method === 'PATCH') { const input = await readBoundedJson(req); operationId = takeOperationId(input, createUuid); ifMatch = requireRevision(input); body = validateConfigUpdate(input); } } else if (assignmentMatch && (method === 'GET' || method === 'PUT')) { const id = decodeLocalId(assignmentMatch[1]); upstreamPath = `/api/ai-hardware/devices/${encodeURIComponent(id)}/agent-assignment`; project = projectDevice; requireEtag = true; expectedStatus = 200; if (method === 'PUT') { const input = await readBoundedJson(req); ensureExactKeys(input, new Set(['revision', 'agent_id', 'client_operation_id'])); operationId = takeOperationId(input, createUuid); ifMatch = requireRevision(input); body = { agent_id: requireLocalId(input.agent_id) }; } } else { sendFailure(res, 404, 404, 'AI_HARDWARE_ROUTE_NOT_FOUND', 'AI hardware route was not found', false); return true; } } const token = await getAccessToken({ fetchImpl }); if (!token) { sendFailure(res, 200, 401, 'AI_HARDWARE_AUTH_REQUIRED', 'Works Square sign-in is required', false); return true; } const idempotencyKey = operationId ? `makelore-${operationId}` : undefined; const call = async (accessToken: string): Promise => { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeoutMs); let finished = false; const finish = () => { if (finished) return; finished = true; clearTimeout(timer); }; try { const response = await fetchImpl(`${apiBaseUrl}${upstreamPath}`, { method, headers: { Accept: 'application/json', Authorization: `Bearer ${accessToken}`, ...(body ? { 'Content-Type': 'application/json' } : {}), ...(idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {}), ...(ifMatch === undefined ? {} : { 'If-Match': `"${ifMatch}"` }), }, body: body ? JSON.stringify(body) : undefined, signal: controller.signal, redirect: 'manual', }); return { response, signal: controller.signal, finish }; } catch (error) { finish(); if (controller.signal.aborted) { throw new SafeRouteError(504, 'AI_HARDWARE_TIMEOUT', 'AI hardware service timed out', true); } throw error; } }; let currentToken = token; let activeCall = await call(currentToken); let response = activeCall.response; if (response.status === 401) { await cancelTimedResponse(activeCall); const refreshed = await getAccessToken({ fetchImpl, forceRefresh: true }); if (refreshed) { currentToken = refreshed; activeCall = await call(currentToken); response = activeCall.response; } } let cachedErrorPayload: unknown; if (response.status === 409 && operationId) { const retryDelay = retryAfterSeconds(response, MAX_RETRY_AFTER_SECONDS); if (retryDelay !== undefined) { cachedErrorPayload = isJsonContentType(response) ? await readSafeErrorPayload(activeCall) : null; const safe = safeUpstreamError(cachedErrorPayload, response.status); if (safe.code === 'ai_hardware_operation_in_progress') { await new Promise((resolve) => setTimeout(resolve, retryDelay * 1000)); activeCall = await call(currentToken); response = activeCall.response; cachedErrorPayload = undefined; } } } if (!response.ok) { const status = response.status; if (status === 404 && upstreamPath === UPSTREAM_ROOT) { await cancelTimedResponse(activeCall); sendFailure(res, 200, 404, 'AI_HARDWARE_DISABLED', 'AI hardware module is not enabled', false); return true; } const retryAfter = retryAfterSeconds(response); const payload = cachedErrorPayload ?? (isJsonContentType(response) ? await readSafeErrorPayload(activeCall) : (await cancelTimedResponse(activeCall), null)); const safe = safeUpstreamError(payload, status); sendFailure( res, 200, status, safe.code, safe.error, safe.retryable, retryAfter, operationId, ); return true; } if (response.status !== expectedStatus || !isJsonContentType(response)) { await cancelTimedResponse(activeCall); throw new SafeRouteError(502, 'AI_HARDWARE_INVALID_RESPONSE', 'AI hardware service returned an invalid response', true); } let revision: number | undefined; try { revision = requireEtag ? revisionFromEtag(response) : undefined; } catch (error) { await cancelTimedResponse(activeCall); throw error; } const projected = project(await readTimedResponse(activeCall)); if (!projected) throw new SafeRouteError(502, 'AI_HARDWARE_INVALID_RESPONSE', 'AI hardware service returned an invalid response', true); if (revision !== undefined) { const dtoRevision = 'config_revision' in projected ? projected.config_revision : projected.assignment_revision; if (dtoRevision !== revision) { throw new SafeRouteError(502, 'AI_HARDWARE_INVALID_RESPONSE', 'AI hardware service returned an invalid response', true); } } sendJson(res, 200, { success: true, data: projected, ...(revision === undefined ? {} : { revision }) }); return true; } catch (error) { if (error instanceof SafeRouteError) { sendFailure(res, 200, error.status, error.code, error.message, error.retryable, undefined, operationId); } else if (error instanceof Error && error.name === 'AbortError') { sendFailure(res, 200, 504, 'AI_HARDWARE_TIMEOUT', 'AI hardware service timed out', true, undefined, operationId); } else { sendFailure(res, 200, 502, 'AI_HARDWARE_UNAVAILABLE', 'AI hardware service is temporarily unavailable', true, undefined, operationId); } return true; } }; } export const handleAiHardwareRoutes = createAiHardwareRouteHandler();