#!/usr/bin/env node 'use strict'; const fs = require('fs'); const ROUTES = { 2: { brand: '长安', subAgentKey: 'changan_new_car_audit', description: '长安新车审核子Agent', agentId: 6, agentName: 'agent_c020c385', }, 11: { brand: '赛力斯', subAgentKey: 'seres_new_car_audit', description: '赛力斯审核子Agent', agentId: 9, agentName: 'agent_6a442e52', }, 16: { brand: '阿维塔', subAgentKey: 'avatr_new_car_audit', description: '阿维塔审核子Agent', agentId: 10, agentName: 'agent_7d380038', }, }; function emit(stage, message, status = 'running', extra = {}) { process.stderr.write(JSON.stringify({ type: 'process_event', stage, message, status, timestamp: new Date().toISOString(), ...extra, }) + '\n'); } function readInput() { const raw = String(process.argv[2] || process.env.SKILL_INPUT || process.env.AIFLOW_SKILL_INPUT || fs.readFileSync(0, 'utf8')).trim(); return raw ? JSON.parse(raw) : {}; } function boolValue(value, fallback) { if (value === undefined || value === null || value === '') return fallback; if (typeof value === 'boolean') return value; if (typeof value === 'number') return value !== 0; return ['1', 'true', 'yes', 'y', 'on'].includes(String(value).toLowerCase()); } function parseMessage(message) { if (message === undefined || message === null || message === '') return {}; if (typeof message === 'string') { try { return JSON.parse(message); } catch { return { text: message }; } } if (typeof message === 'object' && !Array.isArray(message)) return message; return { value: message }; } function normalizeOemId(value) { if (value === undefined || value === null || value === '') return undefined; const text = String(value).trim(); if (!/^\d+$/.test(text)) return undefined; return Number(text); } function pickOemId(input, message) { return normalizeOemId(input.oemId) ?? normalizeOemId(message.oemId) ?? normalizeOemId(message.data && message.data.oemId); } function firstNonEmpty(...values) { for (const value of values) { if (value !== undefined && value !== null && value !== '') return value; } return undefined; } function pickAgentName(input, env, oemId) { const key = String(oemId); const route = ROUTES[oemId]; const agentNames = input.agentNames && typeof input.agentNames === 'object' ? input.agentNames : {}; return firstNonEmpty( agentNames[key], input[`agentName${key}`], env[`OEM_AGENT_NAME_${key}`], route && route.agentName, ); } function buildChatUrl(input, env) { const url = firstNonEmpty(input.chatUrl, env.AIFLOW_NETACLAW_CHAT_URL); if (!url) return ''; try { return new URL(String(url)).toString(); } catch { throw new Error('chatUrl must be an absolute URL'); } } function buildChatRequest(input, message, agentName) { const forwardedMessage = { ...message }; if (boolValue(input.stripCallbackUrl, true)) { delete forwardedMessage.callbackUrl; if (forwardedMessage.data && typeof forwardedMessage.data === 'object' && !Array.isArray(forwardedMessage.data)) { forwardedMessage.data = { ...forwardedMessage.data }; delete forwardedMessage.data.callbackUrl; } } const requestBody = { message: JSON.stringify(forwardedMessage), agentName, }; if (input.sessionId) requestBody.sessionId = String(input.sessionId); if (input.userId) requestBody.userId = String(input.userId); return requestBody; } async function postJson(url, bodyText, headers, timeoutMs, retries) { let lastErr; for (let attempt = 0; attempt <= retries; attempt += 1) { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeoutMs); try { const response = await fetch(url, { method: 'POST', headers, body: bodyText, signal: controller.signal, }); const text = await response.text(); let parsed = text; try { parsed = text ? JSON.parse(text) : {}; } catch { parsed = text; } if (!response.ok) { const err = new Error(`HTTP ${response.status}: ${typeof parsed === 'string' ? parsed.slice(0, 300) : JSON.stringify(parsed).slice(0, 300)}`); err.statusCode = response.status; err.response = parsed; throw err; } return { statusCode: response.status, response: parsed }; } catch (err) { lastErr = err; if (attempt < retries) await new Promise(resolve => setTimeout(resolve, 300 * (attempt + 1))); } finally { clearTimeout(timer); } } throw lastErr; } async function run(input, env) { const message = parseMessage(input.message); const oemId = pickOemId(input, message); const route = ROUTES[oemId]; if (!route) { return { success: false, routed: false, invoked: false, oemId: oemId ?? null, error: `Unsupported oemId: ${oemId ?? 'missing'}. Supported oemId values are 2, 11, 16.`, message, }; } const invoke = boolValue(input.invoke, true); const subAgentName = String(pickAgentName(input, env, oemId) || ''); const base = { success: true, routed: true, invoked: false, oemId, brand: route.brand, subAgentKey: route.subAgentKey, subAgentDescription: route.description, subAgentId: route.agentId, subAgentName, reason: `oemId=${oemId} routed to ${route.description}`, }; if (!invoke) { emit('route', base.reason, 'completed', { oemId, brand: route.brand }); return base; } if (!subAgentName) { return { ...base, success: false, error: `Missing subAgent internal name for oemId=${oemId}. Provide agentNames["${oemId}"], agentName${oemId}, or OEM_AGENT_NAME_${oemId}.`, }; } const chatUrl = buildChatUrl(input, env); if (!chatUrl) { return { ...base, success: false, error: 'Missing chatUrl. Provide input.chatUrl or AIFLOW_NETACLAW_CHAT_URL.', }; } const requestBody = buildChatRequest(input, message, subAgentName); const headers = { 'Content-Type': 'application/json', }; if (input.headers && typeof input.headers === 'object' && !Array.isArray(input.headers)) { Object.assign(headers, input.headers); } const request = { url: chatUrl, method: 'POST', headers, body: requestBody, }; if (boolValue(input.dryRun, false)) { emit('dry-run', `OEM路由dryRun完成: ${route.description}`, 'completed', { oemId, brand: route.brand }); return { ...base, invoked: true, dryRun: true, request, }; } const timeoutMs = Number(input.timeoutMs || env.OEM_ROUTER_TIMEOUT_MS || 300000); const retries = Number(input.retries ?? env.OEM_ROUTER_RETRIES ?? 0); emit('invoke', `调用${route.description}`, 'running', { oemId, brand: route.brand, subAgentName }); const response = await postJson(chatUrl, JSON.stringify(requestBody), headers, timeoutMs, retries); emit('complete', `${route.description}响应完成`, 'completed', { statusCode: response.statusCode }); return { ...base, invoked: true, dryRun: false, statusCode: response.statusCode, response: response.response, request, }; } (async () => { try { const input = readInput(); process.stdout.write(JSON.stringify(await run(input, process.env))); } catch (err) { process.stdout.write(JSON.stringify({ success: false, routed: false, invoked: false, error: err && err.message ? err.message : String(err), statusCode: err && err.statusCode ? err.statusCode : undefined, response: err && err.response ? err.response : undefined, })); } })();