271 lines
7.6 KiB
JavaScript
Raw Permalink Normal View History

2026-05-20 21:39:12 +08:00
#!/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,
}));
}
})();