@faapi/faapi 2.0.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +4195 -3320
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +447 -2
- package/dist/index.js +1715 -772
- package/dist/index.js.map +1 -1
- package/dist/testing.js +24 -1
- package/dist/testing.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -790,7 +790,11 @@ var init_resolveInjection = __esm({
|
|
|
790
790
|
ip: "ip",
|
|
791
791
|
ua: "ua",
|
|
792
792
|
files: "files",
|
|
793
|
-
fields: "fields"
|
|
793
|
+
fields: "fields",
|
|
794
|
+
agent: "agent",
|
|
795
|
+
// Phase 2.3
|
|
796
|
+
agents: "agents"
|
|
797
|
+
// Phase 2.3
|
|
794
798
|
};
|
|
795
799
|
}
|
|
796
800
|
});
|
|
@@ -1219,8 +1223,8 @@ __export(generateSchemaFiles_exports, {
|
|
|
1219
1223
|
getRuntimeSchemaPath: () => getRuntimeSchemaPath,
|
|
1220
1224
|
getSchemaOutputPath: () => getSchemaOutputPath
|
|
1221
1225
|
});
|
|
1222
|
-
import
|
|
1223
|
-
import
|
|
1226
|
+
import path5 from "path";
|
|
1227
|
+
import fs4 from "fs/promises";
|
|
1224
1228
|
function getSchemaOutputPath(sourceFile, dist, rootDir) {
|
|
1225
1229
|
let rel = sourceFile.replace(/\\/g, "/");
|
|
1226
1230
|
if (rel.startsWith("src/")) {
|
|
@@ -1228,7 +1232,7 @@ function getSchemaOutputPath(sourceFile, dist, rootDir) {
|
|
|
1228
1232
|
}
|
|
1229
1233
|
const idx = rel.lastIndexOf("/");
|
|
1230
1234
|
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
1231
|
-
return
|
|
1235
|
+
return path5.resolve(rootDir, dist, relDir, "zod.js");
|
|
1232
1236
|
}
|
|
1233
1237
|
function getRuntimeSchemaPath(filePath, dist, rootDir) {
|
|
1234
1238
|
let rel = filePath.replace(/\\/g, "/");
|
|
@@ -1239,7 +1243,7 @@ function getRuntimeSchemaPath(filePath, dist, rootDir) {
|
|
|
1239
1243
|
}
|
|
1240
1244
|
const idx = rel.lastIndexOf("/");
|
|
1241
1245
|
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
1242
|
-
return
|
|
1246
|
+
return path5.resolve(rootDir, dist, relDir, "zod.js");
|
|
1243
1247
|
}
|
|
1244
1248
|
function getHelpersImportPath(relDir) {
|
|
1245
1249
|
if (!relDir) return `./${HELPERS_FILENAME}`;
|
|
@@ -1289,7 +1293,7 @@ async function generateSchemaFiles(routes, rootDir, dist) {
|
|
|
1289
1293
|
}
|
|
1290
1294
|
const fileEntries = [];
|
|
1291
1295
|
for (const [filePath, fileSources] of sourcesByFile) {
|
|
1292
|
-
const relFile =
|
|
1296
|
+
const relFile = path5.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
1293
1297
|
const outputPath = getSchemaOutputPath(relFile, dist, rootDir);
|
|
1294
1298
|
const allTypes = allTypesByFile.get(filePath) ?? /* @__PURE__ */ new Map();
|
|
1295
1299
|
let relForDir = relFile;
|
|
@@ -1304,7 +1308,7 @@ async function generateSchemaFiles(routes, rootDir, dist) {
|
|
|
1304
1308
|
}
|
|
1305
1309
|
const allSourceCode = fileEntries.map((e) => e.source).join("\n");
|
|
1306
1310
|
if (usesCoerceHelpers(allSourceCode)) {
|
|
1307
|
-
const helpersPath =
|
|
1311
|
+
const helpersPath = path5.resolve(rootDir, dist, HELPERS_FILENAME);
|
|
1308
1312
|
await writeSchemaFile(helpersPath, generateHelpersFileSource());
|
|
1309
1313
|
}
|
|
1310
1314
|
await Promise.all(
|
|
@@ -1312,8 +1316,8 @@ async function generateSchemaFiles(routes, rootDir, dist) {
|
|
|
1312
1316
|
);
|
|
1313
1317
|
}
|
|
1314
1318
|
async function writeSchemaFile(outputPath, source) {
|
|
1315
|
-
await
|
|
1316
|
-
await
|
|
1319
|
+
await fs4.mkdir(path5.dirname(outputPath), { recursive: true });
|
|
1320
|
+
await fs4.writeFile(outputPath, source, "utf-8");
|
|
1317
1321
|
}
|
|
1318
1322
|
var init_generateSchemaFiles = __esm({
|
|
1319
1323
|
"src/cli/generateSchemaFiles.ts"() {
|
|
@@ -1330,175 +1334,86 @@ init_resolveTypeNode();
|
|
|
1330
1334
|
init_inputType();
|
|
1331
1335
|
init_collectRouteSchemaSources();
|
|
1332
1336
|
|
|
1333
|
-
// src/
|
|
1334
|
-
var
|
|
1335
|
-
function
|
|
1336
|
-
const
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
await next();
|
|
1348
|
-
return;
|
|
1349
|
-
}
|
|
1350
|
-
let allowOrigin = null;
|
|
1351
|
-
if (origin === true) {
|
|
1352
|
-
allowOrigin = reqOrigin;
|
|
1353
|
-
} else if (typeof origin === "string") {
|
|
1354
|
-
allowOrigin = reqOrigin === origin ? origin : null;
|
|
1355
|
-
} else if (Array.isArray(origin)) {
|
|
1356
|
-
allowOrigin = origin.includes(reqOrigin) ? reqOrigin : null;
|
|
1357
|
-
}
|
|
1358
|
-
if (!allowOrigin) {
|
|
1359
|
-
await next();
|
|
1360
|
-
return;
|
|
1361
|
-
}
|
|
1362
|
-
ctx.setHeader("Access-Control-Allow-Origin", allowOrigin);
|
|
1363
|
-
if (origin === true || Array.isArray(origin)) {
|
|
1364
|
-
const existingVary = ctx.headers.get("vary");
|
|
1365
|
-
if (existingVary) {
|
|
1366
|
-
if (!existingVary.toLowerCase().includes("origin")) {
|
|
1367
|
-
ctx.setHeader("Vary", `${existingVary}, Origin`);
|
|
1368
|
-
}
|
|
1369
|
-
} else {
|
|
1370
|
-
ctx.setHeader("Vary", "Origin");
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
|
-
ctx.setHeader("Access-Control-Allow-Methods", methods.join(", "));
|
|
1374
|
-
if (allowedHeaders) {
|
|
1375
|
-
ctx.setHeader("Access-Control-Allow-Headers", allowedHeaders.join(", "));
|
|
1376
|
-
} else {
|
|
1377
|
-
const requestHeaders = ctx.headers.get("access-control-request-headers");
|
|
1378
|
-
if (requestHeaders) {
|
|
1379
|
-
ctx.setHeader("Access-Control-Allow-Headers", requestHeaders);
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
if (exposeHeaders && exposeHeaders.length > 0) {
|
|
1383
|
-
ctx.setHeader("Access-Control-Expose-Headers", exposeHeaders.join(", "));
|
|
1384
|
-
}
|
|
1385
|
-
if (credentials) {
|
|
1386
|
-
ctx.setHeader("Access-Control-Allow-Credentials", "true");
|
|
1387
|
-
}
|
|
1388
|
-
if (maxAge !== void 0) {
|
|
1389
|
-
ctx.setHeader("Access-Control-Max-Age", String(maxAge));
|
|
1390
|
-
}
|
|
1391
|
-
if (ctx.method === "OPTIONS") {
|
|
1392
|
-
return new Response(null, { status: 204 });
|
|
1393
|
-
}
|
|
1394
|
-
await next();
|
|
1395
|
-
};
|
|
1337
|
+
// src/injection/toolRegistry.ts
|
|
1338
|
+
var registry = /* @__PURE__ */ new Map();
|
|
1339
|
+
function hydrateToolRegistry(tools) {
|
|
1340
|
+
const next = /* @__PURE__ */ new Map();
|
|
1341
|
+
for (const tool of tools) {
|
|
1342
|
+
next.set(tool.name, tool);
|
|
1343
|
+
}
|
|
1344
|
+
registry = next;
|
|
1345
|
+
}
|
|
1346
|
+
function clearToolRegistry() {
|
|
1347
|
+
registry = /* @__PURE__ */ new Map();
|
|
1348
|
+
}
|
|
1349
|
+
function getTool(name) {
|
|
1350
|
+
return registry.get(name);
|
|
1396
1351
|
}
|
|
1397
1352
|
|
|
1398
|
-
// src/
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
};
|
|
1425
|
-
log(entry, `${ctx.method} ${ctx.path} ${status} ${duration}ms - ${message}`);
|
|
1426
|
-
throw err;
|
|
1353
|
+
// src/injection/agentRegistry.ts
|
|
1354
|
+
var registry2 = /* @__PURE__ */ new Map();
|
|
1355
|
+
function hydrateAgentRegistry(agents) {
|
|
1356
|
+
const next = /* @__PURE__ */ new Map();
|
|
1357
|
+
for (const agent of agents) {
|
|
1358
|
+
next.set(agent.name, agent);
|
|
1359
|
+
}
|
|
1360
|
+
registry2 = next;
|
|
1361
|
+
}
|
|
1362
|
+
function clearAgentRegistry() {
|
|
1363
|
+
registry2 = /* @__PURE__ */ new Map();
|
|
1364
|
+
}
|
|
1365
|
+
function getAgent(name) {
|
|
1366
|
+
return registry2.get(name);
|
|
1367
|
+
}
|
|
1368
|
+
function listAgents() {
|
|
1369
|
+
return Array.from(registry2.values());
|
|
1370
|
+
}
|
|
1371
|
+
function resolveAgentTools(name) {
|
|
1372
|
+
const agent = registry2.get(name);
|
|
1373
|
+
if (!agent) return [];
|
|
1374
|
+
const result = /* @__PURE__ */ new Map();
|
|
1375
|
+
if (agent.tools) {
|
|
1376
|
+
for (const toolName of agent.tools) {
|
|
1377
|
+
const tool = getTool(toolName);
|
|
1378
|
+
if (tool) result.set(tool.name, tool);
|
|
1427
1379
|
}
|
|
1428
|
-
}
|
|
1380
|
+
}
|
|
1381
|
+
return Array.from(result.values());
|
|
1382
|
+
}
|
|
1383
|
+
function resolveSubAgents(name) {
|
|
1384
|
+
const agent = registry2.get(name);
|
|
1385
|
+
if (!agent || !agent.agents) return [];
|
|
1386
|
+
const result = [];
|
|
1387
|
+
for (const agentName of agent.agents) {
|
|
1388
|
+
const subAgent = registry2.get(agentName);
|
|
1389
|
+
if (subAgent) result.push(subAgent);
|
|
1390
|
+
}
|
|
1391
|
+
return result;
|
|
1429
1392
|
}
|
|
1430
1393
|
|
|
1431
|
-
// src/
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
originAgentCluster: true,
|
|
1445
|
-
xPoweredBy: true
|
|
1446
|
-
};
|
|
1447
|
-
function helmet(options = {}) {
|
|
1448
|
-
const opts = { ...DEFAULTS, ...options };
|
|
1449
|
-
return async (ctx, next) => {
|
|
1450
|
-
if (opts.contentSecurityPolicy !== false) {
|
|
1451
|
-
ctx.setHeader("Content-Security-Policy", opts.contentSecurityPolicy);
|
|
1452
|
-
}
|
|
1453
|
-
if (opts.xFrameOptions !== false) {
|
|
1454
|
-
ctx.setHeader("X-Frame-Options", opts.xFrameOptions);
|
|
1455
|
-
}
|
|
1456
|
-
if (opts.xContentTypeOptions) {
|
|
1457
|
-
ctx.setHeader("X-Content-Type-Options", "nosniff");
|
|
1458
|
-
}
|
|
1459
|
-
if (opts.referrerPolicy !== false) {
|
|
1460
|
-
ctx.setHeader("Referrer-Policy", opts.referrerPolicy);
|
|
1461
|
-
}
|
|
1462
|
-
if (opts.strictTransportSecurity !== false) {
|
|
1463
|
-
ctx.setHeader("Strict-Transport-Security", opts.strictTransportSecurity);
|
|
1464
|
-
}
|
|
1465
|
-
if (opts.xDnsPrefetchControl) {
|
|
1466
|
-
ctx.setHeader("X-DNS-Prefetch-Control", "off");
|
|
1467
|
-
}
|
|
1468
|
-
if (opts.xDownloadOptions) {
|
|
1469
|
-
ctx.setHeader("X-Download-Options", "noopen");
|
|
1470
|
-
}
|
|
1471
|
-
if (opts.xPermittedCrossDomainPolicies !== false) {
|
|
1472
|
-
ctx.setHeader("X-Permitted-Cross-Domain-Policies", opts.xPermittedCrossDomainPolicies);
|
|
1473
|
-
}
|
|
1474
|
-
if (opts.crossOriginOpenerPolicy !== false) {
|
|
1475
|
-
ctx.setHeader("Cross-Origin-Opener-Policy", opts.crossOriginOpenerPolicy);
|
|
1476
|
-
}
|
|
1477
|
-
if (opts.crossOriginResourcePolicy !== false) {
|
|
1478
|
-
ctx.setHeader("Cross-Origin-Resource-Policy", opts.crossOriginResourcePolicy);
|
|
1479
|
-
}
|
|
1480
|
-
if (opts.crossOriginEmbedderPolicy !== false) {
|
|
1481
|
-
ctx.setHeader("Cross-Origin-Embedder-Policy", opts.crossOriginEmbedderPolicy);
|
|
1482
|
-
}
|
|
1483
|
-
if (opts.originAgentCluster) {
|
|
1484
|
-
ctx.setHeader("Origin-Agent-Cluster", "?1");
|
|
1485
|
-
}
|
|
1486
|
-
if (opts.xPoweredBy) {
|
|
1487
|
-
ctx.setHeader("X-Powered-By", "faapi");
|
|
1394
|
+
// src/loader/loadAgentModule.ts
|
|
1395
|
+
import fs6 from "fs";
|
|
1396
|
+
|
|
1397
|
+
// src/loader/resolveExports.ts
|
|
1398
|
+
function resolveExport(module, exportName) {
|
|
1399
|
+
if (exportName in module && typeof module[exportName] !== "undefined") {
|
|
1400
|
+
return module[exportName];
|
|
1401
|
+
}
|
|
1402
|
+
const defaultExport = module.default;
|
|
1403
|
+
if (defaultExport !== null && typeof defaultExport === "object") {
|
|
1404
|
+
const value = defaultExport[exportName];
|
|
1405
|
+
if (value !== void 0) {
|
|
1406
|
+
return value;
|
|
1488
1407
|
}
|
|
1489
|
-
|
|
1490
|
-
|
|
1408
|
+
}
|
|
1409
|
+
return void 0;
|
|
1491
1410
|
}
|
|
1492
1411
|
|
|
1493
|
-
// src/config/loadConfig.ts
|
|
1494
|
-
import path2 from "path";
|
|
1495
|
-
import fs from "fs";
|
|
1496
|
-
|
|
1497
1412
|
// src/utils/importWithCacheBust.ts
|
|
1498
1413
|
import { pathToFileURL } from "url";
|
|
1499
1414
|
var loadTs;
|
|
1500
|
-
function setLoadTimestamp(
|
|
1501
|
-
loadTs =
|
|
1415
|
+
function setLoadTimestamp(ts9) {
|
|
1416
|
+
loadTs = ts9;
|
|
1502
1417
|
}
|
|
1503
1418
|
function getVitestImportActual() {
|
|
1504
1419
|
const vi = globalThis.vi;
|
|
@@ -1522,666 +1437,1227 @@ async function importWithCacheBust(filePath, bustViteCache = false) {
|
|
|
1522
1437
|
return await import(url);
|
|
1523
1438
|
}
|
|
1524
1439
|
|
|
1525
|
-
// src/
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
const configProductPath = path2.resolve(rootDir, dist, CONFIG_PRODUCT_FILE);
|
|
1529
|
-
if (fs.existsSync(configProductPath)) {
|
|
1530
|
-
const module = await importWithCacheBust(configProductPath);
|
|
1531
|
-
return module.default ?? {};
|
|
1532
|
-
}
|
|
1533
|
-
const hasSourceConfig = fs.existsSync(path2.join(rootDir, "faapi.config.ts")) || fs.existsSync(path2.join(rootDir, "faapi.config.js"));
|
|
1534
|
-
if (hasSourceConfig) {
|
|
1535
|
-
throw new Error(
|
|
1536
|
-
`[faapi] ${dist}/${CONFIG_PRODUCT_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
|
|
1537
|
-
);
|
|
1538
|
-
}
|
|
1539
|
-
return null;
|
|
1540
|
-
}
|
|
1440
|
+
// src/cli/compileOnDemand.ts
|
|
1441
|
+
import path6 from "path";
|
|
1442
|
+
import fs5 from "fs";
|
|
1541
1443
|
|
|
1542
|
-
// src/cli/
|
|
1543
|
-
import
|
|
1444
|
+
// src/cli/compileDevRoutes.ts
|
|
1445
|
+
import path4 from "path";
|
|
1446
|
+
import fs3 from "fs";
|
|
1447
|
+
import fg from "fast-glob";
|
|
1448
|
+
|
|
1449
|
+
// src/cli/aliasPlugin.ts
|
|
1544
1450
|
import path3 from "path";
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
function
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
const
|
|
1560
|
-
|
|
1561
|
-
|
|
1451
|
+
import fs2 from "fs";
|
|
1452
|
+
|
|
1453
|
+
// src/utils/resolveAlias.ts
|
|
1454
|
+
function resolveAlias(specifier, config) {
|
|
1455
|
+
const candidates = [];
|
|
1456
|
+
for (const [pattern, targets] of Object.entries(config.paths)) {
|
|
1457
|
+
const wildcardIndex = pattern.indexOf("*");
|
|
1458
|
+
if (wildcardIndex === -1) {
|
|
1459
|
+
if (specifier === pattern) {
|
|
1460
|
+
candidates.push(...targets);
|
|
1461
|
+
}
|
|
1462
|
+
continue;
|
|
1463
|
+
}
|
|
1464
|
+
const prefix = pattern.slice(0, wildcardIndex);
|
|
1465
|
+
const suffix = pattern.slice(wildcardIndex + 1);
|
|
1466
|
+
if (specifier.startsWith(prefix) && specifier.endsWith(suffix) && specifier.length >= prefix.length + suffix.length) {
|
|
1467
|
+
const captured = specifier.slice(prefix.length, specifier.length - suffix.length);
|
|
1468
|
+
for (const target of targets) {
|
|
1469
|
+
candidates.push(target.replace("*", captured));
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1562
1472
|
}
|
|
1563
|
-
return
|
|
1473
|
+
return candidates;
|
|
1564
1474
|
}
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1475
|
+
|
|
1476
|
+
// src/utils/readTsconfig.ts
|
|
1477
|
+
import ts6 from "typescript";
|
|
1478
|
+
import path2 from "path";
|
|
1479
|
+
import fs from "fs";
|
|
1480
|
+
function readTsconfig(rootDir) {
|
|
1481
|
+
const tsconfigPath = path2.resolve(rootDir, "tsconfig.json");
|
|
1482
|
+
if (!fs.existsSync(tsconfigPath)) return null;
|
|
1483
|
+
const configFile = ts6.readConfigFile(tsconfigPath, ts6.sys.readFile);
|
|
1484
|
+
if (configFile.error || !configFile.config) return null;
|
|
1485
|
+
const parsed = ts6.parseJsonConfigFileContent(configFile.config, ts6.sys, rootDir);
|
|
1486
|
+
const baseUrl = parsed.options.baseUrl ?? rootDir;
|
|
1487
|
+
const rawPaths = parsed.options.paths;
|
|
1488
|
+
if (!rawPaths) return null;
|
|
1489
|
+
const paths = {};
|
|
1490
|
+
for (const [pattern, targets] of Object.entries(rawPaths)) {
|
|
1491
|
+
paths[pattern] = targets.map((t) => path2.resolve(baseUrl, t));
|
|
1575
1492
|
}
|
|
1576
|
-
|
|
1577
|
-
const value = commentMatch ? commentMatch[1] : raw;
|
|
1578
|
-
return value.trim();
|
|
1493
|
+
return { baseUrl, paths };
|
|
1579
1494
|
}
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
case "t":
|
|
1588
|
-
return " ";
|
|
1589
|
-
case "\\":
|
|
1590
|
-
return "\\";
|
|
1591
|
-
case '"':
|
|
1592
|
-
return '"';
|
|
1593
|
-
default:
|
|
1594
|
-
return ch;
|
|
1595
|
-
}
|
|
1596
|
-
});
|
|
1597
|
-
return escaped.replace(
|
|
1598
|
-
/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g,
|
|
1599
|
-
(_, braced, plain) => {
|
|
1600
|
-
const varName = braced || plain;
|
|
1601
|
-
return env[varName] ?? process.env[varName] ?? "";
|
|
1602
|
-
}
|
|
1603
|
-
);
|
|
1495
|
+
|
|
1496
|
+
// src/cli/aliasPlugin.ts
|
|
1497
|
+
function toProdExtension(filePath) {
|
|
1498
|
+
if (filePath.endsWith(".ts")) return filePath.slice(0, -3) + ".js";
|
|
1499
|
+
if (filePath.endsWith(".tsx")) return filePath.slice(0, -4) + ".js";
|
|
1500
|
+
if (filePath.endsWith(".jsx")) return filePath.slice(0, -4) + ".js";
|
|
1501
|
+
return filePath;
|
|
1604
1502
|
}
|
|
1605
|
-
function
|
|
1606
|
-
const
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
if (!fs2.existsSync(filePath)) continue;
|
|
1612
|
-
const content = fs2.readFileSync(filePath, "utf-8");
|
|
1613
|
-
const parsed = parseEnvFile(content, merged);
|
|
1614
|
-
Object.assign(merged, parsed);
|
|
1615
|
-
}
|
|
1616
|
-
for (const [key, value] of Object.entries(merged)) {
|
|
1617
|
-
if (process.env[key] === void 0) {
|
|
1618
|
-
process.env[key] = value;
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1503
|
+
function toProdImportPath(sourceFile, importer) {
|
|
1504
|
+
const importerDir = path3.dirname(importer);
|
|
1505
|
+
let rel = path3.relative(importerDir, sourceFile);
|
|
1506
|
+
rel = rel.split(path3.sep).join("/");
|
|
1507
|
+
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
1508
|
+
return toProdExtension(rel);
|
|
1621
1509
|
}
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
this.code = code;
|
|
1628
|
-
this.statusCode = statusCode;
|
|
1629
|
-
this.name = "FaapiError";
|
|
1510
|
+
function toRealPath(p) {
|
|
1511
|
+
try {
|
|
1512
|
+
return fs2.realpathSync(p);
|
|
1513
|
+
} catch {
|
|
1514
|
+
return p;
|
|
1630
1515
|
}
|
|
1631
|
-
code;
|
|
1632
|
-
statusCode;
|
|
1633
|
-
};
|
|
1634
|
-
|
|
1635
|
-
// src/errors/httpErrors.ts
|
|
1636
|
-
function deriveStatusCode(issues) {
|
|
1637
|
-
const has400 = issues.some((i) => i.code === "INVALID_FORMAT" || i.code === "MISSING_FIELD");
|
|
1638
|
-
return has400 ? 400 : 422;
|
|
1639
1516
|
}
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1517
|
+
function isInsideDir(filePath, dir) {
|
|
1518
|
+
const rel = path3.relative(dir, filePath);
|
|
1519
|
+
return rel !== "" && !rel.startsWith("..") && !path3.isAbsolute(rel);
|
|
1520
|
+
}
|
|
1521
|
+
var APP_DIR = "src";
|
|
1522
|
+
function toStrippedProdImportPath(sourceFile, rootDir) {
|
|
1523
|
+
const appDirAbs = toRealPath(path3.resolve(rootDir, APP_DIR));
|
|
1524
|
+
const sourceReal = toRealPath(sourceFile);
|
|
1525
|
+
let rel = path3.relative(appDirAbs, sourceReal);
|
|
1526
|
+
rel = rel.split(path3.sep).join("/");
|
|
1527
|
+
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
1528
|
+
return toProdExtension(rel);
|
|
1529
|
+
}
|
|
1530
|
+
var PROD_EXTS = [".js", ".mjs", ".cjs"];
|
|
1531
|
+
var SOURCE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
1532
|
+
var INDEX_EXTS = [
|
|
1533
|
+
"/index.ts",
|
|
1534
|
+
"/index.tsx",
|
|
1535
|
+
"/index.js",
|
|
1536
|
+
"/index.jsx",
|
|
1537
|
+
"/index.mjs",
|
|
1538
|
+
"/index.cjs"
|
|
1539
|
+
];
|
|
1540
|
+
function resolveRelativeSpecifier(importer, specifier) {
|
|
1541
|
+
const importerDir = path3.dirname(importer);
|
|
1542
|
+
const base = path3.resolve(importerDir, specifier);
|
|
1543
|
+
if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
|
|
1544
|
+
return fs2.existsSync(base) ? base : null;
|
|
1652
1545
|
}
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
constructor(method, path14, allowedMethods) {
|
|
1656
|
-
super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path14}`, 405);
|
|
1657
|
-
this.allowedMethods = allowedMethods;
|
|
1658
|
-
this.name = "MethodNotAllowedError";
|
|
1546
|
+
if (/\.(ts|tsx|jsx)$/.test(specifier)) {
|
|
1547
|
+
return fs2.existsSync(base) ? base : null;
|
|
1659
1548
|
}
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
constructor(message) {
|
|
1664
|
-
super("INTERNAL_ERROR", message, 500);
|
|
1665
|
-
this.name = "InternalError";
|
|
1549
|
+
for (const ext of SOURCE_EXTS) {
|
|
1550
|
+
const file = base + ext;
|
|
1551
|
+
if (fs2.existsSync(file)) return file;
|
|
1666
1552
|
}
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
super("MODULE_LOAD_ERROR", `Failed to load module ${filePath}: ${reason}`, 500);
|
|
1671
|
-
this.name = "ModuleLoadError";
|
|
1553
|
+
for (const indexExt of INDEX_EXTS) {
|
|
1554
|
+
const file = base + indexExt;
|
|
1555
|
+
if (fs2.existsSync(file)) return file;
|
|
1672
1556
|
}
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
// src/cli/createAppCore.ts
|
|
1676
|
-
import fs11 from "fs";
|
|
1677
|
-
import path12 from "path";
|
|
1678
|
-
import { PassThrough, Readable as Readable3 } from "stream";
|
|
1679
|
-
|
|
1680
|
-
// src/router/sortRoutes.ts
|
|
1681
|
-
function sortRoutes(routes) {
|
|
1682
|
-
return [...routes].sort((a, b) => {
|
|
1683
|
-
if (a.isDynamic !== b.isDynamic) {
|
|
1684
|
-
return a.isDynamic ? 1 : -1;
|
|
1685
|
-
}
|
|
1686
|
-
if (a.isCatchAll !== b.isCatchAll) {
|
|
1687
|
-
return a.isCatchAll ? 1 : -1;
|
|
1688
|
-
}
|
|
1689
|
-
const aSegments = a.urlPath.split("/").filter(Boolean).length;
|
|
1690
|
-
const bSegments = b.urlPath.split("/").filter(Boolean).length;
|
|
1691
|
-
if (aSegments !== bSegments) {
|
|
1692
|
-
return aSegments - bSegments;
|
|
1693
|
-
}
|
|
1694
|
-
return a.urlPath.localeCompare(b.urlPath);
|
|
1695
|
-
});
|
|
1557
|
+
return null;
|
|
1696
1558
|
}
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1559
|
+
function createAliasPlugin(config, options) {
|
|
1560
|
+
const SPEC_RE = /(\bfrom\s*|import\s*\(\s*)(['"])([^'"]+)\2/g;
|
|
1561
|
+
const appDirAbs = options?.rootDir ? toRealPath(path3.resolve(options.rootDir, APP_DIR)) : null;
|
|
1562
|
+
return {
|
|
1563
|
+
name: "faapi-alias",
|
|
1564
|
+
setup(build) {
|
|
1565
|
+
build.onLoad({ filter: /\.(ts|tsx|js|jsx|mjs|cjs)$/ }, (args) => {
|
|
1566
|
+
let source;
|
|
1567
|
+
try {
|
|
1568
|
+
source = fs2.readFileSync(args.path, "utf8");
|
|
1569
|
+
} catch {
|
|
1570
|
+
return void 0;
|
|
1571
|
+
}
|
|
1572
|
+
const importer = args.path;
|
|
1573
|
+
const importerOutsideAppDir = appDirAbs ? !isInsideDir(importer, appDirAbs) : false;
|
|
1574
|
+
let modified = false;
|
|
1575
|
+
const newSource = source.replace(SPEC_RE, (full, prefix, quote, specifier) => {
|
|
1576
|
+
if (specifier.startsWith("/") || specifier.startsWith("file:") || specifier.startsWith("node:")) {
|
|
1577
|
+
return full;
|
|
1578
|
+
}
|
|
1579
|
+
if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
1580
|
+
const resolved = resolveRelativeSpecifier(importer, specifier);
|
|
1581
|
+
if (resolved) {
|
|
1582
|
+
if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
|
|
1583
|
+
return full;
|
|
1584
|
+
}
|
|
1585
|
+
if (appDirAbs && importerOutsideAppDir && isInsideDir(resolved, appDirAbs)) {
|
|
1586
|
+
modified = true;
|
|
1587
|
+
return `${prefix}${quote}${toStrippedProdImportPath(
|
|
1588
|
+
resolved,
|
|
1589
|
+
options.rootDir
|
|
1590
|
+
)}${quote}`;
|
|
1591
|
+
}
|
|
1592
|
+
modified = true;
|
|
1593
|
+
return `${prefix}${quote}${toProdImportPath(resolved, importer)}${quote}`;
|
|
1594
|
+
}
|
|
1595
|
+
return full;
|
|
1596
|
+
}
|
|
1597
|
+
const candidates = resolveAlias(specifier, config);
|
|
1598
|
+
for (const candidate of candidates) {
|
|
1599
|
+
for (const ext of SOURCE_EXTS) {
|
|
1600
|
+
const file = candidate + ext;
|
|
1601
|
+
if (fs2.existsSync(file)) {
|
|
1602
|
+
modified = true;
|
|
1603
|
+
if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
|
|
1604
|
+
return `${prefix}${quote}${toStrippedProdImportPath(
|
|
1605
|
+
file,
|
|
1606
|
+
options.rootDir
|
|
1607
|
+
)}${quote}`;
|
|
1608
|
+
}
|
|
1609
|
+
return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
for (const indexExt of INDEX_EXTS) {
|
|
1613
|
+
const file = candidate + indexExt;
|
|
1614
|
+
if (fs2.existsSync(file)) {
|
|
1615
|
+
modified = true;
|
|
1616
|
+
if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
|
|
1617
|
+
return `${prefix}${quote}${toStrippedProdImportPath(
|
|
1618
|
+
file,
|
|
1619
|
+
options.rootDir
|
|
1620
|
+
)}${quote}`;
|
|
1621
|
+
}
|
|
1622
|
+
return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
return full;
|
|
1627
|
+
});
|
|
1628
|
+
if (!modified) return void 0;
|
|
1629
|
+
return { contents: newSource, loader: "default" };
|
|
1711
1630
|
});
|
|
1712
1631
|
}
|
|
1713
|
-
}
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
}
|
|
1719
|
-
}
|
|
1720
|
-
return conflicts;
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
function buildAliasPlugins(rootDir) {
|
|
1635
|
+
const tsconfig = readTsconfig(rootDir);
|
|
1636
|
+
return [createAliasPlugin(tsconfig ?? { baseUrl: ".", paths: {} }, { rootDir })];
|
|
1721
1637
|
}
|
|
1722
1638
|
|
|
1723
|
-
// src/
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1639
|
+
// src/cli/compileDevRoutes.ts
|
|
1640
|
+
var APP_DIR2 = "src";
|
|
1641
|
+
async function compileDevRoutes(options) {
|
|
1642
|
+
const { rootDir, dist, files, logLevel = "silent" } = options;
|
|
1643
|
+
const entryPoints = files ?? await fg([`${APP_DIR2}/**/*.ts`], {
|
|
1644
|
+
cwd: rootDir,
|
|
1645
|
+
onlyFiles: true,
|
|
1646
|
+
absolute: true,
|
|
1647
|
+
ignore: ["**/*.test.ts", "**/*.e2e.test.ts", "**/*.d.ts"]
|
|
1648
|
+
});
|
|
1649
|
+
if (entryPoints.length === 0) {
|
|
1650
|
+
return { compiledFiles: [] };
|
|
1651
|
+
}
|
|
1652
|
+
const absDist = path4.resolve(rootDir, dist);
|
|
1653
|
+
await fs3.promises.mkdir(absDist, { recursive: true });
|
|
1654
|
+
const plugins = buildAliasPlugins(rootDir);
|
|
1655
|
+
const esbuild = await import("esbuild");
|
|
1656
|
+
const outbase = path4.resolve(rootDir, APP_DIR2);
|
|
1657
|
+
const result = await esbuild.build({
|
|
1658
|
+
entryPoints,
|
|
1659
|
+
outdir: absDist,
|
|
1660
|
+
outbase,
|
|
1661
|
+
bundle: false,
|
|
1662
|
+
platform: "node",
|
|
1663
|
+
format: "esm",
|
|
1664
|
+
sourcemap: true,
|
|
1665
|
+
packages: "external",
|
|
1666
|
+
plugins,
|
|
1667
|
+
logLevel,
|
|
1668
|
+
write: false
|
|
1669
|
+
});
|
|
1670
|
+
if (result.outputFiles) {
|
|
1671
|
+
await Promise.all(
|
|
1672
|
+
result.outputFiles.map(async (file) => {
|
|
1673
|
+
await fs3.promises.mkdir(path4.dirname(file.path), { recursive: true });
|
|
1674
|
+
const tmp = `${file.path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
1675
|
+
await fs3.promises.writeFile(tmp, file.contents);
|
|
1676
|
+
await fs3.promises.rename(tmp, file.path);
|
|
1677
|
+
})
|
|
1678
|
+
);
|
|
1679
|
+
}
|
|
1680
|
+
return { compiledFiles: entryPoints };
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
// src/cli/compileOnDemand.ts
|
|
1684
|
+
init_generateSchemaFiles();
|
|
1685
|
+
init_generateSchemaFiles();
|
|
1686
|
+
function isProductFresh(sourceAbsPath, productAbsPath) {
|
|
1687
|
+
try {
|
|
1688
|
+
const srcStat = fs5.statSync(sourceAbsPath);
|
|
1689
|
+
const prodStat = fs5.statSync(productAbsPath);
|
|
1690
|
+
return prodStat.mtimeMs >= srcStat.mtimeMs;
|
|
1691
|
+
} catch {
|
|
1692
|
+
return false;
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
var compiledFiles = /* @__PURE__ */ new Set();
|
|
1696
|
+
function clearCompiledFiles() {
|
|
1697
|
+
compiledFiles.clear();
|
|
1698
|
+
}
|
|
1699
|
+
async function ensureCompiled(sourceAbsPath, rootDir, dist) {
|
|
1700
|
+
if (compiledFiles.has(sourceAbsPath)) {
|
|
1701
|
+
return false;
|
|
1702
|
+
}
|
|
1703
|
+
if (!fs5.existsSync(sourceAbsPath)) {
|
|
1704
|
+
return false;
|
|
1705
|
+
}
|
|
1706
|
+
const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);
|
|
1707
|
+
if (productPath && isProductFresh(sourceAbsPath, productPath)) {
|
|
1708
|
+
compiledFiles.add(sourceAbsPath);
|
|
1709
|
+
return false;
|
|
1710
|
+
}
|
|
1711
|
+
await compileDevRoutes({
|
|
1712
|
+
rootDir,
|
|
1713
|
+
dist,
|
|
1714
|
+
files: [sourceAbsPath],
|
|
1715
|
+
logLevel: "silent"
|
|
1716
|
+
});
|
|
1717
|
+
compiledFiles.add(sourceAbsPath);
|
|
1718
|
+
return true;
|
|
1719
|
+
}
|
|
1720
|
+
function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
|
|
1721
|
+
const rel = path6.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
1722
|
+
if (!rel.startsWith("src/")) return null;
|
|
1723
|
+
const relWithoutSrc = rel.slice(4);
|
|
1724
|
+
const jsRel = relWithoutSrc.replace(/\.ts$/, ".js");
|
|
1725
|
+
return path6.resolve(rootDir, dist, jsRel);
|
|
1726
|
+
}
|
|
1727
|
+
var generatedSchemas = /* @__PURE__ */ new Set();
|
|
1728
|
+
function clearGeneratedSchemas() {
|
|
1729
|
+
generatedSchemas.clear();
|
|
1730
|
+
}
|
|
1731
|
+
async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist) {
|
|
1732
|
+
if (generatedSchemas.has(schemaPath)) {
|
|
1733
|
+
return false;
|
|
1734
|
+
}
|
|
1735
|
+
const prodAbsPath = path6.resolve(rootDir, routeFilePath);
|
|
1736
|
+
const sourceAbsPath = prodPathToSourcePath(prodAbsPath, rootDir, dist);
|
|
1737
|
+
if (!fs5.existsSync(sourceAbsPath)) {
|
|
1738
|
+
return false;
|
|
1739
|
+
}
|
|
1740
|
+
if (isProductFresh(sourceAbsPath, schemaPath)) {
|
|
1741
|
+
generatedSchemas.add(schemaPath);
|
|
1742
|
+
return false;
|
|
1743
|
+
}
|
|
1744
|
+
const fileRoutes = routes.filter((r) => r.filePath === routeFilePath);
|
|
1745
|
+
if (fileRoutes.length === 0) {
|
|
1746
|
+
return false;
|
|
1747
|
+
}
|
|
1748
|
+
const sourceRelPath = path6.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
1749
|
+
const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
|
|
1750
|
+
await generateSchemaFiles(sourceRoutes, rootDir, dist);
|
|
1751
|
+
generatedSchemas.add(schemaPath);
|
|
1752
|
+
return true;
|
|
1753
|
+
}
|
|
1754
|
+
async function deleteSchemaFiles(routes, rootDir, dist) {
|
|
1755
|
+
const deleted = /* @__PURE__ */ new Set();
|
|
1734
1756
|
for (const route of routes) {
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
}
|
|
1742
|
-
continue;
|
|
1743
|
-
}
|
|
1744
|
-
const params = matchDynamicPath(route.urlPath, path14, route.paramNames, route.isCatchAll);
|
|
1745
|
-
if (params !== null) {
|
|
1746
|
-
return { route, params };
|
|
1757
|
+
const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);
|
|
1758
|
+
if (deleted.has(schemaPath)) continue;
|
|
1759
|
+
deleted.add(schemaPath);
|
|
1760
|
+
try {
|
|
1761
|
+
await fs5.promises.unlink(schemaPath);
|
|
1762
|
+
} catch {
|
|
1747
1763
|
}
|
|
1748
1764
|
}
|
|
1749
|
-
return null;
|
|
1750
1765
|
}
|
|
1751
|
-
function
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1766
|
+
function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
|
|
1767
|
+
const rel = path6.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
|
|
1768
|
+
let relWithoutDist = rel;
|
|
1769
|
+
if (relWithoutDist.startsWith(`${dist}/`)) {
|
|
1770
|
+
relWithoutDist = relWithoutDist.slice(dist.length + 1);
|
|
1771
|
+
}
|
|
1772
|
+
const srcRel = `src/${relWithoutDist}`;
|
|
1773
|
+
const tsRel = srcRel.replace(/\.js$/, ".ts");
|
|
1774
|
+
const tsAbs = path6.resolve(rootDir, tsRel);
|
|
1775
|
+
if (fs5.existsSync(tsAbs)) return tsAbs;
|
|
1776
|
+
return path6.resolve(rootDir, srcRel);
|
|
1777
|
+
}
|
|
1778
|
+
var devOnDemandEnabled = false;
|
|
1779
|
+
function isDevOnDemandEnabled() {
|
|
1780
|
+
return devOnDemandEnabled;
|
|
1781
|
+
}
|
|
1782
|
+
var devDistDir;
|
|
1783
|
+
function getDevDist() {
|
|
1784
|
+
return devDistDir;
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
// src/loader/loadAgentModule.ts
|
|
1788
|
+
async function loadAgentModule(filePath, hasConfig, hasRun, rootDir) {
|
|
1789
|
+
if (isDevOnDemandEnabled() && rootDir) {
|
|
1790
|
+
const dist = getDevDist();
|
|
1791
|
+
if (dist) {
|
|
1792
|
+
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
1793
|
+
if (sourcePath && fs6.existsSync(sourcePath)) {
|
|
1794
|
+
try {
|
|
1795
|
+
await ensureCompiled(sourcePath, rootDir, dist);
|
|
1796
|
+
} catch (compileErr) {
|
|
1797
|
+
const reason = compileErr instanceof Error ? compileErr.message : String(compileErr);
|
|
1798
|
+
throw new Error(`Failed to compile agent module "${sourcePath}": ${reason}`, {
|
|
1799
|
+
cause: compileErr
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
1756
1802
|
}
|
|
1757
|
-
continue;
|
|
1758
|
-
}
|
|
1759
|
-
const params = matchDynamicPath(route.urlPath, path14, route.paramNames, route.isCatchAll);
|
|
1760
|
-
if (params !== null) {
|
|
1761
|
-
return { route, params };
|
|
1762
1803
|
}
|
|
1763
1804
|
}
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1805
|
+
let module;
|
|
1806
|
+
try {
|
|
1807
|
+
module = await importWithCacheBust(filePath, isDevOnDemandEnabled());
|
|
1808
|
+
} catch (err) {
|
|
1809
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
1810
|
+
throw new Error(`Failed to load agent module "${filePath}": ${reason}`, { cause: err });
|
|
1811
|
+
}
|
|
1812
|
+
let config;
|
|
1813
|
+
if (hasConfig) {
|
|
1814
|
+
const configExport = resolveExport(module, "config");
|
|
1815
|
+
if (configExport === void 0) {
|
|
1816
|
+
throw new Error(
|
|
1817
|
+
`Agent module "${filePath}" does not export "config" (hasConfig=true but export missing).`
|
|
1818
|
+
);
|
|
1773
1819
|
}
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
params2[paramName] = pathSeg;
|
|
1781
|
-
} else if (patternSeg !== pathSeg) {
|
|
1782
|
-
return null;
|
|
1820
|
+
if (typeof configExport === "function") {
|
|
1821
|
+
const returned = configExport();
|
|
1822
|
+
if (returned === null || typeof returned !== "object") {
|
|
1823
|
+
throw new Error(
|
|
1824
|
+
`Agent module "${filePath}" config() did not return an object (got ${returned === null ? "null" : typeof returned}).`
|
|
1825
|
+
);
|
|
1783
1826
|
}
|
|
1827
|
+
config = returned;
|
|
1828
|
+
} else if (typeof configExport === "object" && configExport !== null) {
|
|
1829
|
+
config = configExport;
|
|
1830
|
+
} else {
|
|
1831
|
+
throw new Error(
|
|
1832
|
+
`Agent module "${filePath}" config export must be an object or function, got ${typeof configExport}.`
|
|
1833
|
+
);
|
|
1784
1834
|
}
|
|
1785
|
-
const catchAllValue = pathSegments.slice(nonCatchAllCount).join("/");
|
|
1786
|
-
const catchAllParamName = patternSegments[nonCatchAllCount].slice(4);
|
|
1787
|
-
params2[catchAllParamName] = catchAllValue;
|
|
1788
|
-
if (Object.keys(params2).length !== paramNames.length) {
|
|
1789
|
-
return null;
|
|
1790
|
-
}
|
|
1791
|
-
return params2;
|
|
1792
|
-
}
|
|
1793
|
-
if (patternSegments.length !== pathSegments.length) {
|
|
1794
|
-
return null;
|
|
1795
1835
|
}
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
const
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
} else if (patternSeg !== pathSeg) {
|
|
1804
|
-
return null;
|
|
1836
|
+
let run;
|
|
1837
|
+
if (hasRun) {
|
|
1838
|
+
const runExport = resolveExport(module, "run");
|
|
1839
|
+
if (typeof runExport !== "function") {
|
|
1840
|
+
throw new Error(
|
|
1841
|
+
`Agent module "${filePath}" does not export a valid "run" function (hasRun=true). Expected a function, got ${runExport === void 0 ? "undefined" : typeof runExport}.`
|
|
1842
|
+
);
|
|
1805
1843
|
}
|
|
1844
|
+
run = runExport;
|
|
1806
1845
|
}
|
|
1807
|
-
|
|
1808
|
-
return null;
|
|
1809
|
-
}
|
|
1810
|
-
return params;
|
|
1846
|
+
return { config, run };
|
|
1811
1847
|
}
|
|
1812
1848
|
|
|
1813
|
-
// src/loader/
|
|
1814
|
-
import
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1849
|
+
// src/loader/loadToolModule.ts
|
|
1850
|
+
import fs7 from "fs";
|
|
1851
|
+
async function loadToolModule(filePath, functionName, rootDir) {
|
|
1852
|
+
if (isDevOnDemandEnabled() && rootDir) {
|
|
1853
|
+
const dist = getDevDist();
|
|
1854
|
+
if (dist) {
|
|
1855
|
+
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
1856
|
+
if (sourcePath && fs7.existsSync(sourcePath)) {
|
|
1857
|
+
try {
|
|
1858
|
+
await ensureCompiled(sourcePath, rootDir, dist);
|
|
1859
|
+
} catch (compileErr) {
|
|
1860
|
+
const reason = compileErr instanceof Error ? compileErr.message : String(compileErr);
|
|
1861
|
+
throw new Error(`Failed to compile tool module "${sourcePath}": ${reason}`, {
|
|
1862
|
+
cause: compileErr
|
|
1863
|
+
});
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1826
1866
|
}
|
|
1827
1867
|
}
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1868
|
+
let module;
|
|
1869
|
+
try {
|
|
1870
|
+
module = await importWithCacheBust(filePath, isDevOnDemandEnabled());
|
|
1871
|
+
} catch (err) {
|
|
1872
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
1873
|
+
throw new Error(`Failed to load tool module "${filePath}": ${reason}`, { cause: err });
|
|
1874
|
+
}
|
|
1875
|
+
const handler = resolveExport(module, functionName);
|
|
1876
|
+
if (typeof handler !== "function") {
|
|
1834
1877
|
throw new Error(
|
|
1835
|
-
`
|
|
1878
|
+
`Tool module "${filePath}" does not export a valid function for "${functionName}". Expected a function, got ${handler === void 0 ? "undefined" : typeof handler}.`
|
|
1836
1879
|
);
|
|
1837
1880
|
}
|
|
1881
|
+
return { handler, functionName };
|
|
1838
1882
|
}
|
|
1839
1883
|
|
|
1840
|
-
// src/
|
|
1841
|
-
import
|
|
1842
|
-
import fs7 from "fs";
|
|
1884
|
+
// src/loader/loadToolSchema.ts
|
|
1885
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1843
1886
|
|
|
1844
|
-
// src/cli/
|
|
1845
|
-
import
|
|
1846
|
-
import
|
|
1847
|
-
import
|
|
1848
|
-
|
|
1849
|
-
// src/cli/aliasPlugin.ts
|
|
1850
|
-
import path5 from "path";
|
|
1851
|
-
import fs4 from "fs";
|
|
1887
|
+
// src/cli/generateToolArtifacts.ts
|
|
1888
|
+
import path7 from "path";
|
|
1889
|
+
import fs8 from "fs/promises";
|
|
1890
|
+
import { existsSync } from "fs";
|
|
1852
1891
|
|
|
1853
|
-
// src/
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1892
|
+
// src/ast/extractToolMetadata.ts
|
|
1893
|
+
import ts7 from "typescript";
|
|
1894
|
+
function extractToolMetadata(program, filePath, functionName, pathMeta) {
|
|
1895
|
+
const sourceFile = program.getSourceFile(filePath);
|
|
1896
|
+
if (!sourceFile) return null;
|
|
1897
|
+
const found = findExportedFunction(sourceFile, functionName);
|
|
1898
|
+
if (!found) return null;
|
|
1899
|
+
const { fn, jsDocOwner } = found;
|
|
1900
|
+
const jsDoc = getJSDocFromNode(jsDocOwner);
|
|
1901
|
+
const description = extractDescription(jsDoc);
|
|
1902
|
+
const toolNameOverride = extractToolTagValue(jsDoc);
|
|
1903
|
+
const inputTypeName = getFirstParamTypeName(fn, sourceFile);
|
|
1904
|
+
return {
|
|
1905
|
+
name: toolNameOverride ?? pathMeta.name,
|
|
1906
|
+
description,
|
|
1907
|
+
inputTypeName,
|
|
1908
|
+
filePath: pathMeta.filePath,
|
|
1909
|
+
functionName
|
|
1910
|
+
};
|
|
1911
|
+
}
|
|
1912
|
+
function findExportedFunction(sourceFile, functionName) {
|
|
1913
|
+
let result = null;
|
|
1914
|
+
ts7.forEachChild(sourceFile, (node) => {
|
|
1915
|
+
if (result) return;
|
|
1916
|
+
if (ts7.isFunctionDeclaration(node) && hasExportModifier(node) && node.name?.text === functionName) {
|
|
1917
|
+
result = { fn: node, jsDocOwner: node };
|
|
1918
|
+
return;
|
|
1863
1919
|
}
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1920
|
+
if (ts7.isVariableStatement(node) && hasExportModifier(node)) {
|
|
1921
|
+
for (const decl of node.declarationList.declarations) {
|
|
1922
|
+
if (result) break;
|
|
1923
|
+
const nameText = ts7.isIdentifier(decl.name) ? decl.name.text : decl.name.getText(sourceFile);
|
|
1924
|
+
if (nameText !== functionName || !decl.initializer) continue;
|
|
1925
|
+
if (ts7.isArrowFunction(decl.initializer) || ts7.isFunctionExpression(decl.initializer)) {
|
|
1926
|
+
result = { fn: decl.initializer, jsDocOwner: node };
|
|
1927
|
+
}
|
|
1870
1928
|
}
|
|
1871
1929
|
}
|
|
1872
|
-
}
|
|
1873
|
-
return
|
|
1930
|
+
});
|
|
1931
|
+
return result;
|
|
1874
1932
|
}
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
function
|
|
1881
|
-
const
|
|
1882
|
-
if (
|
|
1883
|
-
const
|
|
1884
|
-
if (
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
if (!
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1933
|
+
function hasExportModifier(node) {
|
|
1934
|
+
if (!ts7.canHaveModifiers(node)) return false;
|
|
1935
|
+
const modifiers = ts7.getModifiers(node);
|
|
1936
|
+
return !!modifiers?.some((m) => m.kind === ts7.SyntaxKind.ExportKeyword);
|
|
1937
|
+
}
|
|
1938
|
+
function getJSDocFromNode(node) {
|
|
1939
|
+
const apiDocs = ts7.getJSDocCommentsAndTags(node).filter((entry) => ts7.isJSDoc(entry));
|
|
1940
|
+
if (apiDocs.length > 0) return apiDocs[0];
|
|
1941
|
+
const directDocs = node.jsDoc;
|
|
1942
|
+
if (directDocs && directDocs.length > 0) return directDocs[0];
|
|
1943
|
+
return void 0;
|
|
1944
|
+
}
|
|
1945
|
+
function extractDescription(jsDoc) {
|
|
1946
|
+
if (!jsDoc) return void 0;
|
|
1947
|
+
if (typeof jsDoc.comment !== "string") return void 0;
|
|
1948
|
+
const trimmed = jsDoc.comment.trim();
|
|
1949
|
+
return trimmed || void 0;
|
|
1950
|
+
}
|
|
1951
|
+
function extractToolTagValue(jsDoc) {
|
|
1952
|
+
if (!jsDoc || !jsDoc.tags) return void 0;
|
|
1953
|
+
for (const tag of jsDoc.tags) {
|
|
1954
|
+
if (tag.tagName.text !== "tool") continue;
|
|
1955
|
+
if (typeof tag.comment !== "string") return void 0;
|
|
1956
|
+
const text = tag.comment.trim();
|
|
1957
|
+
if (!text) return void 0;
|
|
1958
|
+
const cleaned = text.replace(/^\{|\}$/g, "").trim();
|
|
1959
|
+
return cleaned || void 0;
|
|
1892
1960
|
}
|
|
1893
|
-
return
|
|
1961
|
+
return void 0;
|
|
1962
|
+
}
|
|
1963
|
+
function getFirstParamTypeName(fn, sourceFile) {
|
|
1964
|
+
const firstParam = fn.parameters[0];
|
|
1965
|
+
if (!firstParam) return void 0;
|
|
1966
|
+
if (!firstParam.type) return void 0;
|
|
1967
|
+
if (!ts7.isTypeReferenceNode(firstParam.type)) return void 0;
|
|
1968
|
+
return firstParam.type.typeName.getText(sourceFile);
|
|
1894
1969
|
}
|
|
1895
1970
|
|
|
1896
|
-
// src/cli/
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1971
|
+
// src/cli/generateToolArtifacts.ts
|
|
1972
|
+
init_createProgram();
|
|
1973
|
+
init_extractHandlerTypes();
|
|
1974
|
+
init_generateZodSchema();
|
|
1975
|
+
init_generateSchemaFiles();
|
|
1976
|
+
var TOOLS_FILE = "faapi-tools.js";
|
|
1977
|
+
function getToolSchemaOutputPath(sourceFile, dist, rootDir) {
|
|
1978
|
+
let rel = sourceFile.replace(/\\/g, "/");
|
|
1979
|
+
if (rel.startsWith("src/")) {
|
|
1980
|
+
rel = rel.slice(4);
|
|
1981
|
+
}
|
|
1982
|
+
const idx = rel.lastIndexOf("/");
|
|
1983
|
+
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
1984
|
+
return path7.resolve(rootDir, dist, relDir, "zod.js");
|
|
1902
1985
|
}
|
|
1903
|
-
function
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
if (
|
|
1908
|
-
|
|
1986
|
+
function getRuntimeToolSchemaPath(filePath, dist, rootDir) {
|
|
1987
|
+
let rel = filePath.replace(/\\/g, "/");
|
|
1988
|
+
if (rel.startsWith("src/")) {
|
|
1989
|
+
rel = rel.slice(4);
|
|
1990
|
+
} else if (rel.startsWith(`${dist}/`)) {
|
|
1991
|
+
rel = rel.slice(dist.length + 1);
|
|
1992
|
+
}
|
|
1993
|
+
const idx = rel.lastIndexOf("/");
|
|
1994
|
+
const relDir = idx >= 0 ? rel.slice(0, idx) : "";
|
|
1995
|
+
return path7.resolve(rootDir, dist, relDir, "zod.js");
|
|
1909
1996
|
}
|
|
1910
|
-
function
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
return p;
|
|
1997
|
+
function toProdFilePath(filePath, dist) {
|
|
1998
|
+
let rel = filePath.replace(/\\/g, "/");
|
|
1999
|
+
if (rel.startsWith("src/")) {
|
|
2000
|
+
rel = rel.slice(4);
|
|
1915
2001
|
}
|
|
2002
|
+
const jsPath = rel.replace(/\.ts$/, ".js");
|
|
2003
|
+
return jsPath.startsWith(`${dist}/`) ? jsPath : `${dist}/${jsPath}`;
|
|
1916
2004
|
}
|
|
1917
|
-
function
|
|
1918
|
-
|
|
1919
|
-
|
|
2005
|
+
function serializeTools(tools, dist = "dist") {
|
|
2006
|
+
return tools.map((t) => ({
|
|
2007
|
+
name: t.name,
|
|
2008
|
+
functionName: t.functionName,
|
|
2009
|
+
description: t.description,
|
|
2010
|
+
inputTypeName: t.inputTypeName,
|
|
2011
|
+
filePath: toProdFilePath(t.filePath, dist)
|
|
2012
|
+
}));
|
|
2013
|
+
}
|
|
2014
|
+
async function writeToolsModule(manifest, outputPath) {
|
|
2015
|
+
const dir = path7.dirname(outputPath);
|
|
2016
|
+
await fs8.mkdir(dir, { recursive: true });
|
|
2017
|
+
const content = `// \u81EA\u52A8\u751F\u6210,\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91(faapi build/dev \u4EA7\u7269)
|
|
2018
|
+
export const tools = ${JSON.stringify(manifest, null, 2)};
|
|
2019
|
+
`;
|
|
2020
|
+
await fs8.writeFile(outputPath, content, "utf-8");
|
|
2021
|
+
}
|
|
2022
|
+
function hydrateTools(manifest) {
|
|
2023
|
+
return manifest.map((t) => ({
|
|
2024
|
+
name: t.name,
|
|
2025
|
+
functionName: t.functionName,
|
|
2026
|
+
description: t.description ?? void 0,
|
|
2027
|
+
inputTypeName: t.inputTypeName ?? void 0,
|
|
2028
|
+
filePath: t.filePath
|
|
2029
|
+
}));
|
|
2030
|
+
}
|
|
2031
|
+
function collectToolSchemaSources(tools, rootDir) {
|
|
2032
|
+
const toolsByFile = /* @__PURE__ */ new Map();
|
|
2033
|
+
for (const tool of tools) {
|
|
2034
|
+
if (!tool.inputTypeName) continue;
|
|
2035
|
+
const absPath = path7.resolve(rootDir, tool.filePath);
|
|
2036
|
+
let list = toolsByFile.get(absPath);
|
|
2037
|
+
if (!list) {
|
|
2038
|
+
list = [];
|
|
2039
|
+
toolsByFile.set(absPath, list);
|
|
2040
|
+
}
|
|
2041
|
+
list.push(tool);
|
|
2042
|
+
}
|
|
2043
|
+
const allTypesByFile = /* @__PURE__ */ new Map();
|
|
2044
|
+
for (const filePath of toolsByFile.keys()) {
|
|
2045
|
+
const program = createProgram(filePath);
|
|
2046
|
+
const allTypes = extractAllTypes(program, filePath);
|
|
2047
|
+
allTypesByFile.set(filePath, allTypes);
|
|
2048
|
+
}
|
|
2049
|
+
const sources = [];
|
|
2050
|
+
for (const [filePath, fileTools] of toolsByFile) {
|
|
2051
|
+
const program = createProgram(filePath);
|
|
2052
|
+
for (const tool of fileTools) {
|
|
2053
|
+
const inputTypeName = tool.inputTypeName;
|
|
2054
|
+
const typeInfo = extractTypeInfo(program, filePath, inputTypeName);
|
|
2055
|
+
sources.push({
|
|
2056
|
+
name: tool.name,
|
|
2057
|
+
filePath,
|
|
2058
|
+
schemaName: inputTypeName,
|
|
2059
|
+
// schema 名 = inputTypeName
|
|
2060
|
+
typeInfo
|
|
2061
|
+
});
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
return { sources, allTypesByFile };
|
|
1920
2065
|
}
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
const
|
|
1924
|
-
const
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
2066
|
+
function generateToolSchemaFileSource(sources, allTypes, helpersImportPath) {
|
|
2067
|
+
const resolveType = (name) => allTypes.get(name)?.runtimeType;
|
|
2068
|
+
const lines = ["import { z } from 'zod';"];
|
|
2069
|
+
const schemaBlocks = [];
|
|
2070
|
+
for (const source of sources) {
|
|
2071
|
+
const { schemaName, typeInfo } = source;
|
|
2072
|
+
if (!typeInfo) {
|
|
2073
|
+
continue;
|
|
2074
|
+
}
|
|
2075
|
+
const coerce = false;
|
|
2076
|
+
const block = [`// ${source.name} \u2192 ${schemaName}`];
|
|
2077
|
+
const schemaCode = generateZodSchemaSource(typeInfo, resolveType, schemaName, coerce).replace(
|
|
2078
|
+
/^import \{ z \} from 'zod';\s*\n\s*\n/,
|
|
2079
|
+
""
|
|
2080
|
+
);
|
|
2081
|
+
block.push(schemaCode);
|
|
2082
|
+
block.push("");
|
|
2083
|
+
schemaBlocks.push(block.join("\n"));
|
|
2084
|
+
}
|
|
2085
|
+
const allSchemaCode = schemaBlocks.join("\n");
|
|
2086
|
+
if (helpersImportPath && usesCoerceHelpers(allSchemaCode)) {
|
|
2087
|
+
lines.push(
|
|
2088
|
+
`import { coerceNumber, coerceBoolean, coerceMap, coerceSet } from '${helpersImportPath}';`
|
|
2089
|
+
);
|
|
2090
|
+
}
|
|
2091
|
+
lines.push("");
|
|
2092
|
+
lines.push(...schemaBlocks);
|
|
2093
|
+
return lines.join("\n").replace(/\n+$/, "\n");
|
|
1929
2094
|
}
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
2095
|
+
async function maybeGenerateHelpers(allSourceCode, distDir) {
|
|
2096
|
+
if (!usesCoerceHelpers(allSourceCode)) return;
|
|
2097
|
+
const helpersPath = path7.resolve(distDir, HELPERS_FILENAME);
|
|
2098
|
+
if (existsSync(helpersPath)) return;
|
|
2099
|
+
await fs8.mkdir(path7.dirname(helpersPath), { recursive: true });
|
|
2100
|
+
await fs8.writeFile(helpersPath, generateHelpersFileSource(), "utf-8");
|
|
2101
|
+
}
|
|
2102
|
+
async function generateToolArtifacts(tools, rootDir, dist, options) {
|
|
2103
|
+
const metadata = [];
|
|
2104
|
+
for (const manifest of tools) {
|
|
2105
|
+
const absPath = path7.resolve(rootDir, manifest.filePath);
|
|
2106
|
+
const program = createProgram(absPath);
|
|
2107
|
+
const result = extractToolMetadata(program, absPath, manifest.functionName, {
|
|
2108
|
+
name: manifest.name,
|
|
2109
|
+
filePath: manifest.filePath
|
|
2110
|
+
});
|
|
2111
|
+
if (result) {
|
|
2112
|
+
metadata.push(result);
|
|
2113
|
+
}
|
|
1945
2114
|
}
|
|
1946
|
-
|
|
1947
|
-
|
|
2115
|
+
const serialized = serializeTools(metadata, dist);
|
|
2116
|
+
const toolsPath = path7.resolve(rootDir, dist, TOOLS_FILE);
|
|
2117
|
+
await writeToolsModule(serialized, toolsPath);
|
|
2118
|
+
if (options?.skipSchema) {
|
|
2119
|
+
return metadata;
|
|
1948
2120
|
}
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
if (fs4.existsSync(file)) return file;
|
|
2121
|
+
if (metadata.length === 0) {
|
|
2122
|
+
return metadata;
|
|
1952
2123
|
}
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
2124
|
+
const { sources, allTypesByFile } = collectToolSchemaSources(metadata, rootDir);
|
|
2125
|
+
if (sources.length === 0) {
|
|
2126
|
+
return metadata;
|
|
1956
2127
|
}
|
|
1957
|
-
|
|
2128
|
+
const sourcesByFile = /* @__PURE__ */ new Map();
|
|
2129
|
+
for (const source of sources) {
|
|
2130
|
+
let list = sourcesByFile.get(source.filePath);
|
|
2131
|
+
if (!list) {
|
|
2132
|
+
list = [];
|
|
2133
|
+
sourcesByFile.set(source.filePath, list);
|
|
2134
|
+
}
|
|
2135
|
+
list.push(source);
|
|
2136
|
+
}
|
|
2137
|
+
const fileEntries = [];
|
|
2138
|
+
for (const [filePath, fileSources] of sourcesByFile) {
|
|
2139
|
+
const relFile = path7.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
2140
|
+
const outputPath = getToolSchemaOutputPath(relFile, dist, rootDir);
|
|
2141
|
+
const allTypes = allTypesByFile.get(filePath) ?? /* @__PURE__ */ new Map();
|
|
2142
|
+
let relForDir = relFile;
|
|
2143
|
+
if (relForDir.startsWith("src/")) {
|
|
2144
|
+
relForDir = relForDir.slice(4);
|
|
2145
|
+
}
|
|
2146
|
+
const dirIdx = relForDir.lastIndexOf("/");
|
|
2147
|
+
const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : "";
|
|
2148
|
+
const helpersImportPath = getHelpersImportPath(zodRelDir);
|
|
2149
|
+
const source = generateToolSchemaFileSource(fileSources, allTypes, helpersImportPath);
|
|
2150
|
+
fileEntries.push({ outputPath, source });
|
|
2151
|
+
}
|
|
2152
|
+
const allSourceCode = fileEntries.map((e) => e.source).join("\n");
|
|
2153
|
+
const distDir = path7.resolve(rootDir, dist);
|
|
2154
|
+
await maybeGenerateHelpers(allSourceCode, distDir);
|
|
2155
|
+
await Promise.all(
|
|
2156
|
+
fileEntries.map(({ outputPath, source }) => writeToolSchemaFile(outputPath, source))
|
|
2157
|
+
);
|
|
2158
|
+
return metadata;
|
|
1958
2159
|
}
|
|
1959
|
-
function
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
2160
|
+
async function writeToolSchemaFile(outputPath, source) {
|
|
2161
|
+
await fs8.mkdir(path7.dirname(outputPath), { recursive: true });
|
|
2162
|
+
await fs8.writeFile(outputPath, source, "utf-8");
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
// src/loader/loadToolSchema.ts
|
|
2166
|
+
function getDist() {
|
|
2167
|
+
if (isDevOnDemandEnabled()) {
|
|
2168
|
+
return getDevDist() ?? ".faapi";
|
|
2169
|
+
}
|
|
2170
|
+
return process.env.FAAPI_DIST ?? "dist";
|
|
2171
|
+
}
|
|
2172
|
+
async function loadToolSchema(tool, rootDir) {
|
|
2173
|
+
if (!tool.inputTypeName) return void 0;
|
|
2174
|
+
const schemaName = `${tool.inputTypeName}Schema`;
|
|
2175
|
+
const dist = getDist();
|
|
2176
|
+
const zodPath = getRuntimeToolSchemaPath(tool.filePath, dist, rootDir ?? process.cwd());
|
|
2177
|
+
if (!existsSync2(zodPath)) return void 0;
|
|
2178
|
+
try {
|
|
2179
|
+
const mod = await importWithCacheBust(zodPath, isDevOnDemandEnabled());
|
|
2180
|
+
const schema = mod[`${tool.inputTypeName}Schema`];
|
|
2181
|
+
if (!schema) return void 0;
|
|
2182
|
+
return { schema, schemaName };
|
|
2183
|
+
} catch {
|
|
2184
|
+
return void 0;
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
// src/injection/agentHandle.ts
|
|
2189
|
+
var currentFactory = null;
|
|
2190
|
+
function registerAgentHandleFactory(factory) {
|
|
2191
|
+
currentFactory = factory;
|
|
2192
|
+
}
|
|
2193
|
+
function getAgentHandle(ctx) {
|
|
2194
|
+
if (currentFactory === null) return void 0;
|
|
2195
|
+
return currentFactory(ctx);
|
|
2196
|
+
}
|
|
2197
|
+
function clearAgentHandleFactory() {
|
|
2198
|
+
currentFactory = null;
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
// src/middleware/cors.ts
|
|
2202
|
+
var DEFAULT_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
2203
|
+
function cors(options = {}) {
|
|
2204
|
+
const {
|
|
2205
|
+
origin = true,
|
|
2206
|
+
methods = DEFAULT_METHODS,
|
|
2207
|
+
allowedHeaders,
|
|
2208
|
+
exposeHeaders,
|
|
2209
|
+
credentials = false,
|
|
2210
|
+
maxAge
|
|
2211
|
+
} = options;
|
|
2212
|
+
return async (ctx, next) => {
|
|
2213
|
+
const reqOrigin = ctx.headers.get("origin");
|
|
2214
|
+
if (!reqOrigin) {
|
|
2215
|
+
await next();
|
|
2216
|
+
return;
|
|
2217
|
+
}
|
|
2218
|
+
let allowOrigin = null;
|
|
2219
|
+
if (origin === true) {
|
|
2220
|
+
allowOrigin = reqOrigin;
|
|
2221
|
+
} else if (typeof origin === "string") {
|
|
2222
|
+
allowOrigin = reqOrigin === origin ? origin : null;
|
|
2223
|
+
} else if (Array.isArray(origin)) {
|
|
2224
|
+
allowOrigin = origin.includes(reqOrigin) ? reqOrigin : null;
|
|
2225
|
+
}
|
|
2226
|
+
if (!allowOrigin) {
|
|
2227
|
+
await next();
|
|
2228
|
+
return;
|
|
2229
|
+
}
|
|
2230
|
+
ctx.setHeader("Access-Control-Allow-Origin", allowOrigin);
|
|
2231
|
+
if (origin === true || Array.isArray(origin)) {
|
|
2232
|
+
const existingVary = ctx.headers.get("vary");
|
|
2233
|
+
if (existingVary) {
|
|
2234
|
+
if (!existingVary.toLowerCase().includes("origin")) {
|
|
2235
|
+
ctx.setHeader("Vary", `${existingVary}, Origin`);
|
|
1971
2236
|
}
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2237
|
+
} else {
|
|
2238
|
+
ctx.setHeader("Vary", "Origin");
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
ctx.setHeader("Access-Control-Allow-Methods", methods.join(", "));
|
|
2242
|
+
if (allowedHeaders) {
|
|
2243
|
+
ctx.setHeader("Access-Control-Allow-Headers", allowedHeaders.join(", "));
|
|
2244
|
+
} else {
|
|
2245
|
+
const requestHeaders = ctx.headers.get("access-control-request-headers");
|
|
2246
|
+
if (requestHeaders) {
|
|
2247
|
+
ctx.setHeader("Access-Control-Allow-Headers", requestHeaders);
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
if (exposeHeaders && exposeHeaders.length > 0) {
|
|
2251
|
+
ctx.setHeader("Access-Control-Expose-Headers", exposeHeaders.join(", "));
|
|
2252
|
+
}
|
|
2253
|
+
if (credentials) {
|
|
2254
|
+
ctx.setHeader("Access-Control-Allow-Credentials", "true");
|
|
2255
|
+
}
|
|
2256
|
+
if (maxAge !== void 0) {
|
|
2257
|
+
ctx.setHeader("Access-Control-Max-Age", String(maxAge));
|
|
2258
|
+
}
|
|
2259
|
+
if (ctx.method === "OPTIONS") {
|
|
2260
|
+
return new Response(null, { status: 204 });
|
|
2261
|
+
}
|
|
2262
|
+
await next();
|
|
2263
|
+
};
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
// src/middleware/logger.ts
|
|
2267
|
+
function logger(options = {}) {
|
|
2268
|
+
return async (ctx, next) => {
|
|
2269
|
+
const log = options.log ?? console.log;
|
|
2270
|
+
const start = Date.now();
|
|
2271
|
+
try {
|
|
2272
|
+
const response = await next();
|
|
2273
|
+
const duration = Date.now() - start;
|
|
2274
|
+
const entry = {
|
|
2275
|
+
method: ctx.method,
|
|
2276
|
+
path: ctx.path,
|
|
2277
|
+
status: response.status,
|
|
2278
|
+
durationMs: duration
|
|
2279
|
+
};
|
|
2280
|
+
log(entry, `${ctx.method} ${ctx.path} ${response.status} ${duration}ms`);
|
|
2281
|
+
return response;
|
|
2282
|
+
} catch (err) {
|
|
2283
|
+
const duration = Date.now() - start;
|
|
2284
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2285
|
+
const status = err?.statusCode ?? 500;
|
|
2286
|
+
const entry = {
|
|
2287
|
+
method: ctx.method,
|
|
2288
|
+
path: ctx.path,
|
|
2289
|
+
status,
|
|
2290
|
+
durationMs: duration,
|
|
2291
|
+
error: message
|
|
2292
|
+
};
|
|
2293
|
+
log(entry, `${ctx.method} ${ctx.path} ${status} ${duration}ms - ${message}`);
|
|
2294
|
+
throw err;
|
|
2295
|
+
}
|
|
2296
|
+
};
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
// src/middleware/helmet.ts
|
|
2300
|
+
var DEFAULTS = {
|
|
2301
|
+
contentSecurityPolicy: "default-src 'self'",
|
|
2302
|
+
xFrameOptions: "SAMEORIGIN",
|
|
2303
|
+
xContentTypeOptions: true,
|
|
2304
|
+
referrerPolicy: "no-referrer",
|
|
2305
|
+
strictTransportSecurity: "max-age=31536000; includeSubDomains",
|
|
2306
|
+
xDnsPrefetchControl: true,
|
|
2307
|
+
xDownloadOptions: true,
|
|
2308
|
+
xPermittedCrossDomainPolicies: "none",
|
|
2309
|
+
crossOriginOpenerPolicy: "same-origin",
|
|
2310
|
+
crossOriginResourcePolicy: "same-origin",
|
|
2311
|
+
crossOriginEmbedderPolicy: false,
|
|
2312
|
+
originAgentCluster: true,
|
|
2313
|
+
xPoweredBy: true
|
|
2314
|
+
};
|
|
2315
|
+
function helmet(options = {}) {
|
|
2316
|
+
const opts = { ...DEFAULTS, ...options };
|
|
2317
|
+
return async (ctx, next) => {
|
|
2318
|
+
if (opts.contentSecurityPolicy !== false) {
|
|
2319
|
+
ctx.setHeader("Content-Security-Policy", opts.contentSecurityPolicy);
|
|
2320
|
+
}
|
|
2321
|
+
if (opts.xFrameOptions !== false) {
|
|
2322
|
+
ctx.setHeader("X-Frame-Options", opts.xFrameOptions);
|
|
2323
|
+
}
|
|
2324
|
+
if (opts.xContentTypeOptions) {
|
|
2325
|
+
ctx.setHeader("X-Content-Type-Options", "nosniff");
|
|
2326
|
+
}
|
|
2327
|
+
if (opts.referrerPolicy !== false) {
|
|
2328
|
+
ctx.setHeader("Referrer-Policy", opts.referrerPolicy);
|
|
2329
|
+
}
|
|
2330
|
+
if (opts.strictTransportSecurity !== false) {
|
|
2331
|
+
ctx.setHeader("Strict-Transport-Security", opts.strictTransportSecurity);
|
|
2332
|
+
}
|
|
2333
|
+
if (opts.xDnsPrefetchControl) {
|
|
2334
|
+
ctx.setHeader("X-DNS-Prefetch-Control", "off");
|
|
2335
|
+
}
|
|
2336
|
+
if (opts.xDownloadOptions) {
|
|
2337
|
+
ctx.setHeader("X-Download-Options", "noopen");
|
|
2338
|
+
}
|
|
2339
|
+
if (opts.xPermittedCrossDomainPolicies !== false) {
|
|
2340
|
+
ctx.setHeader("X-Permitted-Cross-Domain-Policies", opts.xPermittedCrossDomainPolicies);
|
|
2341
|
+
}
|
|
2342
|
+
if (opts.crossOriginOpenerPolicy !== false) {
|
|
2343
|
+
ctx.setHeader("Cross-Origin-Opener-Policy", opts.crossOriginOpenerPolicy);
|
|
2344
|
+
}
|
|
2345
|
+
if (opts.crossOriginResourcePolicy !== false) {
|
|
2346
|
+
ctx.setHeader("Cross-Origin-Resource-Policy", opts.crossOriginResourcePolicy);
|
|
2347
|
+
}
|
|
2348
|
+
if (opts.crossOriginEmbedderPolicy !== false) {
|
|
2349
|
+
ctx.setHeader("Cross-Origin-Embedder-Policy", opts.crossOriginEmbedderPolicy);
|
|
2350
|
+
}
|
|
2351
|
+
if (opts.originAgentCluster) {
|
|
2352
|
+
ctx.setHeader("Origin-Agent-Cluster", "?1");
|
|
2353
|
+
}
|
|
2354
|
+
if (opts.xPoweredBy) {
|
|
2355
|
+
ctx.setHeader("X-Powered-By", "faapi");
|
|
2031
2356
|
}
|
|
2357
|
+
return await next();
|
|
2032
2358
|
};
|
|
2033
2359
|
}
|
|
2034
|
-
function buildAliasPlugins(rootDir) {
|
|
2035
|
-
const tsconfig = readTsconfig(rootDir);
|
|
2036
|
-
return [createAliasPlugin(tsconfig ?? { baseUrl: ".", paths: {} }, { rootDir })];
|
|
2037
|
-
}
|
|
2038
2360
|
|
|
2039
|
-
// src/
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
});
|
|
2049
|
-
if (entryPoints.length === 0) {
|
|
2050
|
-
return { compiledFiles: [] };
|
|
2361
|
+
// src/config/loadConfig.ts
|
|
2362
|
+
import path8 from "path";
|
|
2363
|
+
import fs9 from "fs";
|
|
2364
|
+
var CONFIG_PRODUCT_FILE = "faapi-config.js";
|
|
2365
|
+
async function loadConfig(rootDir, dist) {
|
|
2366
|
+
const configProductPath = path8.resolve(rootDir, dist, CONFIG_PRODUCT_FILE);
|
|
2367
|
+
if (fs9.existsSync(configProductPath)) {
|
|
2368
|
+
const module = await importWithCacheBust(configProductPath);
|
|
2369
|
+
return module.default ?? {};
|
|
2051
2370
|
}
|
|
2052
|
-
const
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
const outbase = path6.resolve(rootDir, APP_DIR2);
|
|
2057
|
-
const result = await esbuild.build({
|
|
2058
|
-
entryPoints,
|
|
2059
|
-
outdir: absDist,
|
|
2060
|
-
outbase,
|
|
2061
|
-
bundle: false,
|
|
2062
|
-
platform: "node",
|
|
2063
|
-
format: "esm",
|
|
2064
|
-
sourcemap: true,
|
|
2065
|
-
packages: "external",
|
|
2066
|
-
plugins,
|
|
2067
|
-
logLevel,
|
|
2068
|
-
write: false
|
|
2069
|
-
});
|
|
2070
|
-
if (result.outputFiles) {
|
|
2071
|
-
await Promise.all(
|
|
2072
|
-
result.outputFiles.map(async (file) => {
|
|
2073
|
-
await fs5.promises.mkdir(path6.dirname(file.path), { recursive: true });
|
|
2074
|
-
const tmp = `${file.path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
2075
|
-
await fs5.promises.writeFile(tmp, file.contents);
|
|
2076
|
-
await fs5.promises.rename(tmp, file.path);
|
|
2077
|
-
})
|
|
2371
|
+
const hasSourceConfig = fs9.existsSync(path8.join(rootDir, "faapi.config.ts")) || fs9.existsSync(path8.join(rootDir, "faapi.config.js"));
|
|
2372
|
+
if (hasSourceConfig) {
|
|
2373
|
+
throw new Error(
|
|
2374
|
+
`[faapi] ${dist}/${CONFIG_PRODUCT_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
|
|
2078
2375
|
);
|
|
2079
2376
|
}
|
|
2080
|
-
return
|
|
2377
|
+
return null;
|
|
2081
2378
|
}
|
|
2082
2379
|
|
|
2083
|
-
// src/cli/
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
function
|
|
2087
|
-
|
|
2088
|
-
const srcStat = fs7.statSync(sourceAbsPath);
|
|
2089
|
-
const prodStat = fs7.statSync(productAbsPath);
|
|
2090
|
-
return prodStat.mtimeMs >= srcStat.mtimeMs;
|
|
2091
|
-
} catch {
|
|
2092
|
-
return false;
|
|
2093
|
-
}
|
|
2380
|
+
// src/cli/loadEnv.ts
|
|
2381
|
+
import fs10 from "fs";
|
|
2382
|
+
import path9 from "path";
|
|
2383
|
+
function resolveEnv() {
|
|
2384
|
+
return process.env.NODE_ENV || "development";
|
|
2094
2385
|
}
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
compiledFiles.clear();
|
|
2386
|
+
function getEnvFiles(env) {
|
|
2387
|
+
return [".env", ".env.local", `.env.${env}`, `.env.${env}.local`];
|
|
2098
2388
|
}
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2389
|
+
function parseEnvFile(content, fileVars) {
|
|
2390
|
+
const result = {};
|
|
2391
|
+
const lines = content.replace(/\r\n/g, "\n").split("\n");
|
|
2392
|
+
for (const line of lines) {
|
|
2393
|
+
const trimmed = line.trim();
|
|
2394
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
2395
|
+
const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(trimmed);
|
|
2396
|
+
if (!match) continue;
|
|
2397
|
+
const [, key, rawValue] = match;
|
|
2398
|
+
const value = parseValue(rawValue, { ...fileVars, ...result });
|
|
2399
|
+
result[key] = value;
|
|
2102
2400
|
}
|
|
2103
|
-
|
|
2104
|
-
|
|
2401
|
+
return result;
|
|
2402
|
+
}
|
|
2403
|
+
function parseValue(raw, env) {
|
|
2404
|
+
if (raw === "") return "";
|
|
2405
|
+
if (raw[0] === "'") {
|
|
2406
|
+
const end = raw.indexOf("'", 1);
|
|
2407
|
+
return end === -1 ? raw.slice(1) : raw.slice(1, end);
|
|
2105
2408
|
}
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
return
|
|
2409
|
+
if (raw[0] === '"') {
|
|
2410
|
+
const match = /^"((?:\\.|[^"\\])*)"/.exec(raw);
|
|
2411
|
+
const inner = match ? match[1] : raw.slice(1);
|
|
2412
|
+
return expandEscapesAndVars(inner, env);
|
|
2110
2413
|
}
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2414
|
+
const commentMatch = /^(.*?)(\s+#.*)$/.exec(raw);
|
|
2415
|
+
const value = commentMatch ? commentMatch[1] : raw;
|
|
2416
|
+
return value.trim();
|
|
2417
|
+
}
|
|
2418
|
+
function expandEscapesAndVars(str, env) {
|
|
2419
|
+
const escaped = str.replace(/\\(.)/g, (_, ch) => {
|
|
2420
|
+
switch (ch) {
|
|
2421
|
+
case "n":
|
|
2422
|
+
return "\n";
|
|
2423
|
+
case "r":
|
|
2424
|
+
return "\r";
|
|
2425
|
+
case "t":
|
|
2426
|
+
return " ";
|
|
2427
|
+
case "\\":
|
|
2428
|
+
return "\\";
|
|
2429
|
+
case '"':
|
|
2430
|
+
return '"';
|
|
2431
|
+
default:
|
|
2432
|
+
return ch;
|
|
2433
|
+
}
|
|
2116
2434
|
});
|
|
2117
|
-
|
|
2118
|
-
|
|
2435
|
+
return escaped.replace(
|
|
2436
|
+
/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g,
|
|
2437
|
+
(_, braced, plain) => {
|
|
2438
|
+
const varName = braced || plain;
|
|
2439
|
+
return env[varName] ?? process.env[varName] ?? "";
|
|
2440
|
+
}
|
|
2441
|
+
);
|
|
2119
2442
|
}
|
|
2120
|
-
function
|
|
2121
|
-
const
|
|
2122
|
-
|
|
2123
|
-
const
|
|
2124
|
-
const
|
|
2125
|
-
|
|
2443
|
+
function loadEnv(rootDir) {
|
|
2444
|
+
const env = resolveEnv();
|
|
2445
|
+
const files = getEnvFiles(env);
|
|
2446
|
+
const merged = {};
|
|
2447
|
+
for (const file of files) {
|
|
2448
|
+
const filePath = path9.join(rootDir, file);
|
|
2449
|
+
if (!fs10.existsSync(filePath)) continue;
|
|
2450
|
+
const content = fs10.readFileSync(filePath, "utf-8");
|
|
2451
|
+
const parsed = parseEnvFile(content, merged);
|
|
2452
|
+
Object.assign(merged, parsed);
|
|
2453
|
+
}
|
|
2454
|
+
for (const [key, value] of Object.entries(merged)) {
|
|
2455
|
+
if (process.env[key] === void 0) {
|
|
2456
|
+
process.env[key] = value;
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2126
2459
|
}
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2460
|
+
|
|
2461
|
+
// src/errors/FaapiError.ts
|
|
2462
|
+
var FaapiError = class extends Error {
|
|
2463
|
+
constructor(code, message, statusCode) {
|
|
2464
|
+
super(message);
|
|
2465
|
+
this.code = code;
|
|
2466
|
+
this.statusCode = statusCode;
|
|
2467
|
+
this.name = "FaapiError";
|
|
2468
|
+
}
|
|
2469
|
+
code;
|
|
2470
|
+
statusCode;
|
|
2471
|
+
};
|
|
2472
|
+
|
|
2473
|
+
// src/errors/httpErrors.ts
|
|
2474
|
+
function deriveStatusCode(issues) {
|
|
2475
|
+
const has400 = issues.some((i) => i.code === "INVALID_FORMAT" || i.code === "MISSING_FIELD");
|
|
2476
|
+
return has400 ? 400 : 422;
|
|
2130
2477
|
}
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2478
|
+
var ValidationError = class extends FaapiError {
|
|
2479
|
+
constructor(message, issues) {
|
|
2480
|
+
super("VALIDATION_ERROR", message, deriveStatusCode(issues));
|
|
2481
|
+
this.issues = issues;
|
|
2482
|
+
this.name = "ValidationError";
|
|
2134
2483
|
}
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2484
|
+
issues;
|
|
2485
|
+
};
|
|
2486
|
+
var RouteNotFoundError = class extends FaapiError {
|
|
2487
|
+
constructor(path18) {
|
|
2488
|
+
super("ROUTE_NOT_FOUND", `Route not found: ${path18}`, 404);
|
|
2489
|
+
this.name = "RouteNotFoundError";
|
|
2139
2490
|
}
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2491
|
+
};
|
|
2492
|
+
var MethodNotAllowedError = class extends FaapiError {
|
|
2493
|
+
constructor(method, path18, allowedMethods) {
|
|
2494
|
+
super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path18}`, 405);
|
|
2495
|
+
this.allowedMethods = allowedMethods;
|
|
2496
|
+
this.name = "MethodNotAllowedError";
|
|
2143
2497
|
}
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2498
|
+
allowedMethods;
|
|
2499
|
+
};
|
|
2500
|
+
var InternalError = class extends FaapiError {
|
|
2501
|
+
constructor(message) {
|
|
2502
|
+
super("INTERNAL_ERROR", message, 500);
|
|
2503
|
+
this.name = "InternalError";
|
|
2504
|
+
}
|
|
2505
|
+
};
|
|
2506
|
+
var ModuleLoadError = class extends FaapiError {
|
|
2507
|
+
constructor(filePath, reason) {
|
|
2508
|
+
super("MODULE_LOAD_ERROR", `Failed to load module ${filePath}: ${reason}`, 500);
|
|
2509
|
+
this.name = "ModuleLoadError";
|
|
2510
|
+
}
|
|
2511
|
+
};
|
|
2512
|
+
|
|
2513
|
+
// src/cli/createAppCore.ts
|
|
2514
|
+
import fs15 from "fs";
|
|
2515
|
+
import path14 from "path";
|
|
2516
|
+
import { PassThrough, Readable as Readable3 } from "stream";
|
|
2517
|
+
|
|
2518
|
+
// src/router/sortRoutes.ts
|
|
2519
|
+
function sortRoutes(routes) {
|
|
2520
|
+
return [...routes].sort((a, b) => {
|
|
2521
|
+
if (a.isDynamic !== b.isDynamic) {
|
|
2522
|
+
return a.isDynamic ? 1 : -1;
|
|
2523
|
+
}
|
|
2524
|
+
if (a.isCatchAll !== b.isCatchAll) {
|
|
2525
|
+
return a.isCatchAll ? 1 : -1;
|
|
2526
|
+
}
|
|
2527
|
+
const aSegments = a.urlPath.split("/").filter(Boolean).length;
|
|
2528
|
+
const bSegments = b.urlPath.split("/").filter(Boolean).length;
|
|
2529
|
+
if (aSegments !== bSegments) {
|
|
2530
|
+
return aSegments - bSegments;
|
|
2531
|
+
}
|
|
2532
|
+
return a.urlPath.localeCompare(b.urlPath);
|
|
2533
|
+
});
|
|
2534
|
+
}
|
|
2535
|
+
|
|
2536
|
+
// src/router/detectRouteConflicts.ts
|
|
2537
|
+
function detectRouteConflicts(routes) {
|
|
2538
|
+
const map = /* @__PURE__ */ new Map();
|
|
2539
|
+
for (const route of routes) {
|
|
2540
|
+
const key = `${route.method} ${route.urlPath}`;
|
|
2541
|
+
const existing = map.get(key);
|
|
2542
|
+
if (existing) {
|
|
2543
|
+
existing.files.push(route.filePath);
|
|
2544
|
+
} else {
|
|
2545
|
+
map.set(key, {
|
|
2546
|
+
method: route.method,
|
|
2547
|
+
urlPath: route.urlPath,
|
|
2548
|
+
files: [route.filePath]
|
|
2549
|
+
});
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
const conflicts = [];
|
|
2553
|
+
for (const conflict of map.values()) {
|
|
2554
|
+
if (conflict.files.length > 1) {
|
|
2555
|
+
conflicts.push(conflict);
|
|
2556
|
+
}
|
|
2147
2557
|
}
|
|
2148
|
-
|
|
2149
|
-
const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
|
|
2150
|
-
await generateSchemaFiles(sourceRoutes, rootDir, dist);
|
|
2151
|
-
generatedSchemas.add(schemaPath);
|
|
2152
|
-
return true;
|
|
2558
|
+
return conflicts;
|
|
2153
2559
|
}
|
|
2154
|
-
|
|
2155
|
-
|
|
2560
|
+
|
|
2561
|
+
// src/server/createServer.ts
|
|
2562
|
+
import {
|
|
2563
|
+
createServer as createHttpServer
|
|
2564
|
+
} from "http";
|
|
2565
|
+
import { createSecureServer as createHttp2SecureServer } from "http2";
|
|
2566
|
+
import { readFileSync } from "fs";
|
|
2567
|
+
import { Readable as Readable2 } from "stream";
|
|
2568
|
+
import path11 from "path";
|
|
2569
|
+
|
|
2570
|
+
// src/router/matchRoute.ts
|
|
2571
|
+
function matchRoute(routes, method, path18) {
|
|
2156
2572
|
for (const route of routes) {
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2573
|
+
if (route.method !== method) {
|
|
2574
|
+
continue;
|
|
2575
|
+
}
|
|
2576
|
+
if (!route.isDynamic) {
|
|
2577
|
+
if (route.urlPath === path18) {
|
|
2578
|
+
return { route, params: {} };
|
|
2579
|
+
}
|
|
2580
|
+
continue;
|
|
2581
|
+
}
|
|
2582
|
+
const params = matchDynamicPath(route.urlPath, path18, route.paramNames, route.isCatchAll);
|
|
2583
|
+
if (params !== null) {
|
|
2584
|
+
return { route, params };
|
|
2163
2585
|
}
|
|
2164
2586
|
}
|
|
2587
|
+
return null;
|
|
2165
2588
|
}
|
|
2166
|
-
function
|
|
2167
|
-
const
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2589
|
+
function matchWsRoute(wsRoutes, path18) {
|
|
2590
|
+
for (const route of wsRoutes) {
|
|
2591
|
+
if (!route.isDynamic) {
|
|
2592
|
+
if (route.urlPath === path18) {
|
|
2593
|
+
return { route, params: {} };
|
|
2594
|
+
}
|
|
2595
|
+
continue;
|
|
2596
|
+
}
|
|
2597
|
+
const params = matchDynamicPath(route.urlPath, path18, route.paramNames, route.isCatchAll);
|
|
2598
|
+
if (params !== null) {
|
|
2599
|
+
return { route, params };
|
|
2600
|
+
}
|
|
2171
2601
|
}
|
|
2172
|
-
|
|
2173
|
-
const tsRel = srcRel.replace(/\.js$/, ".ts");
|
|
2174
|
-
const tsAbs = path8.resolve(rootDir, tsRel);
|
|
2175
|
-
if (fs7.existsSync(tsAbs)) return tsAbs;
|
|
2176
|
-
return path8.resolve(rootDir, srcRel);
|
|
2602
|
+
return null;
|
|
2177
2603
|
}
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2604
|
+
function matchDynamicPath(pattern, path18, paramNames, isCatchAll) {
|
|
2605
|
+
const patternSegments = pattern.split("/").filter(Boolean);
|
|
2606
|
+
const pathSegments = path18.split("/").filter(Boolean);
|
|
2607
|
+
if (isCatchAll) {
|
|
2608
|
+
const nonCatchAllCount = patternSegments.length - 1;
|
|
2609
|
+
if (pathSegments.length <= nonCatchAllCount) {
|
|
2610
|
+
return null;
|
|
2611
|
+
}
|
|
2612
|
+
const params2 = {};
|
|
2613
|
+
for (let i = 0; i < nonCatchAllCount; i++) {
|
|
2614
|
+
const patternSeg = patternSegments[i];
|
|
2615
|
+
const pathSeg = pathSegments[i];
|
|
2616
|
+
if (patternSeg.startsWith(":")) {
|
|
2617
|
+
const paramName = patternSeg.slice(1);
|
|
2618
|
+
params2[paramName] = pathSeg;
|
|
2619
|
+
} else if (patternSeg !== pathSeg) {
|
|
2620
|
+
return null;
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
const catchAllValue = pathSegments.slice(nonCatchAllCount).join("/");
|
|
2624
|
+
const catchAllParamName = patternSegments[nonCatchAllCount].slice(4);
|
|
2625
|
+
params2[catchAllParamName] = catchAllValue;
|
|
2626
|
+
if (Object.keys(params2).length !== paramNames.length) {
|
|
2627
|
+
return null;
|
|
2628
|
+
}
|
|
2629
|
+
return params2;
|
|
2630
|
+
}
|
|
2631
|
+
if (patternSegments.length !== pathSegments.length) {
|
|
2632
|
+
return null;
|
|
2633
|
+
}
|
|
2634
|
+
const params = {};
|
|
2635
|
+
for (let i = 0; i < patternSegments.length; i++) {
|
|
2636
|
+
const patternSeg = patternSegments[i];
|
|
2637
|
+
const pathSeg = pathSegments[i];
|
|
2638
|
+
if (patternSeg.startsWith(":")) {
|
|
2639
|
+
const paramName = patternSeg.slice(1);
|
|
2640
|
+
params[paramName] = pathSeg;
|
|
2641
|
+
} else if (patternSeg !== pathSeg) {
|
|
2642
|
+
return null;
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
if (Object.keys(params).length !== paramNames.length) {
|
|
2646
|
+
return null;
|
|
2647
|
+
}
|
|
2648
|
+
return params;
|
|
2181
2649
|
}
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2650
|
+
|
|
2651
|
+
// src/loader/loadRouteModule.ts
|
|
2652
|
+
import fs11 from "fs";
|
|
2653
|
+
|
|
2654
|
+
// src/loader/validateRouteModule.ts
|
|
2655
|
+
function validateRouteModule(value, method, filePath) {
|
|
2656
|
+
if (typeof value !== "function") {
|
|
2657
|
+
throw new Error(
|
|
2658
|
+
`Route module "${filePath}" does not export a valid handler for method "${method}". Expected a function, got ${typeof value}.`
|
|
2659
|
+
);
|
|
2660
|
+
}
|
|
2185
2661
|
}
|
|
2186
2662
|
|
|
2187
2663
|
// src/loader/loadRouteModule.ts
|
|
@@ -2190,7 +2666,7 @@ async function loadRouteModule(filePath, method, rootDir) {
|
|
|
2190
2666
|
const dist = getDevDist();
|
|
2191
2667
|
if (dist) {
|
|
2192
2668
|
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
2193
|
-
if (sourcePath &&
|
|
2669
|
+
if (sourcePath && fs11.existsSync(sourcePath)) {
|
|
2194
2670
|
try {
|
|
2195
2671
|
await ensureCompiled(sourcePath, rootDir, dist);
|
|
2196
2672
|
} catch (compileErr) {
|
|
@@ -2684,6 +3160,12 @@ function getBuiltinInjectionValue(type, ctx, body) {
|
|
|
2684
3160
|
return body.fields;
|
|
2685
3161
|
}
|
|
2686
3162
|
return {};
|
|
3163
|
+
// Phase 2.3:注入所有已注册 agent 元数据列表
|
|
3164
|
+
case "agents":
|
|
3165
|
+
return listAgents();
|
|
3166
|
+
// Phase 3.5:调 @faapi/agent 插件注册的工厂获取 AgentHandle
|
|
3167
|
+
case "agent":
|
|
3168
|
+
return getAgentHandle(ctx);
|
|
2687
3169
|
default:
|
|
2688
3170
|
return void 0;
|
|
2689
3171
|
}
|
|
@@ -2867,9 +3349,9 @@ async function validateInput(schemaPath, method, inputType, input) {
|
|
|
2867
3349
|
function mapZodIssues(error) {
|
|
2868
3350
|
return error.issues.map((issue) => {
|
|
2869
3351
|
const code = mapZodCode(issue.code, issue.message);
|
|
2870
|
-
const
|
|
3352
|
+
const path18 = issue.path.map(String).join(".") || "";
|
|
2871
3353
|
return {
|
|
2872
|
-
path:
|
|
3354
|
+
path: path18,
|
|
2873
3355
|
code,
|
|
2874
3356
|
expected: issue.expected ?? mapExpectedFromMessage(issue.message),
|
|
2875
3357
|
received: issue.received ?? mapReceivedFromMessage(issue.message),
|
|
@@ -2931,9 +3413,9 @@ function getClientIp(req) {
|
|
|
2931
3413
|
}
|
|
2932
3414
|
|
|
2933
3415
|
// src/server/handleWsUpgrade.ts
|
|
2934
|
-
import
|
|
3416
|
+
import fs12 from "fs";
|
|
2935
3417
|
import { WebSocketServer, WebSocket } from "ws";
|
|
2936
|
-
import
|
|
3418
|
+
import path10 from "path";
|
|
2937
3419
|
|
|
2938
3420
|
// src/errors/formatErrorResponse.ts
|
|
2939
3421
|
function formatErrorResponse(error) {
|
|
@@ -3100,7 +3582,7 @@ async function loadWsHandler(filePath, ctx, rootDir) {
|
|
|
3100
3582
|
const dist = getDevDist();
|
|
3101
3583
|
if (dist) {
|
|
3102
3584
|
const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
|
|
3103
|
-
if (sourcePath &&
|
|
3585
|
+
if (sourcePath && fs12.existsSync(sourcePath)) {
|
|
3104
3586
|
await ensureCompiled(sourcePath, rootDir, dist);
|
|
3105
3587
|
}
|
|
3106
3588
|
}
|
|
@@ -3180,7 +3662,7 @@ function attachWebSocket(options) {
|
|
|
3180
3662
|
const finalHandler = async () => {
|
|
3181
3663
|
let handlers;
|
|
3182
3664
|
try {
|
|
3183
|
-
const absoluteFilePath =
|
|
3665
|
+
const absoluteFilePath = path10.resolve(rootDir, route.filePath);
|
|
3184
3666
|
handlers = await loadWsHandler(absoluteFilePath, ctx, rootDir);
|
|
3185
3667
|
} catch (err) {
|
|
3186
3668
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -3279,15 +3761,15 @@ function limitStreamSize(stream, maxSize) {
|
|
|
3279
3761
|
}
|
|
3280
3762
|
});
|
|
3281
3763
|
}
|
|
3282
|
-
function findAllowedMethods(routes,
|
|
3764
|
+
function findAllowedMethods(routes, path18) {
|
|
3283
3765
|
const methods = /* @__PURE__ */ new Set();
|
|
3284
3766
|
for (const route of routes) {
|
|
3285
|
-
if (route.urlPath ===
|
|
3767
|
+
if (route.urlPath === path18) {
|
|
3286
3768
|
methods.add(route.method);
|
|
3287
3769
|
continue;
|
|
3288
3770
|
}
|
|
3289
3771
|
if (route.isDynamic) {
|
|
3290
|
-
const params = matchDynamicPath(route.urlPath,
|
|
3772
|
+
const params = matchDynamicPath(route.urlPath, path18, route.paramNames, route.isCatchAll);
|
|
3291
3773
|
if (params !== null) {
|
|
3292
3774
|
methods.add(route.method);
|
|
3293
3775
|
}
|
|
@@ -3373,7 +3855,7 @@ async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares,
|
|
|
3373
3855
|
}
|
|
3374
3856
|
ctx.params = match.params;
|
|
3375
3857
|
const { route } = match;
|
|
3376
|
-
const absoluteFilePath =
|
|
3858
|
+
const absoluteFilePath = path11.resolve(rootDir, route.filePath);
|
|
3377
3859
|
const routeModule = await loadRouteModule(absoluteFilePath, route.method, rootDir);
|
|
3378
3860
|
const input = await resolveInput(route.method, request);
|
|
3379
3861
|
const inputType = getInputTypeForMethod(route.method);
|
|
@@ -3463,8 +3945,8 @@ function applyPluginWrappers(server, handlerWrappers, upgradeWrappers) {
|
|
|
3463
3945
|
}
|
|
3464
3946
|
|
|
3465
3947
|
// src/cli/generateRoutes.ts
|
|
3466
|
-
import
|
|
3467
|
-
import
|
|
3948
|
+
import fs13 from "fs";
|
|
3949
|
+
import path12 from "path";
|
|
3468
3950
|
async function hydrateRoutes(manifest) {
|
|
3469
3951
|
const hydrateRoute = (serialized) => ({
|
|
3470
3952
|
method: serialized.method,
|
|
@@ -3488,6 +3970,266 @@ async function hydrateRoutes(manifest) {
|
|
|
3488
3970
|
return { routes, wsRoutes };
|
|
3489
3971
|
}
|
|
3490
3972
|
|
|
3973
|
+
// src/cli/generateAgentArtifacts.ts
|
|
3974
|
+
import path13 from "path";
|
|
3975
|
+
import fs14 from "fs/promises";
|
|
3976
|
+
|
|
3977
|
+
// src/ast/extractAgentMetadata.ts
|
|
3978
|
+
import ts8 from "typescript";
|
|
3979
|
+
function extractAgentMetadata(program, filePath, pathMeta) {
|
|
3980
|
+
const sourceFile = program.getSourceFile(filePath);
|
|
3981
|
+
if (!sourceFile) return null;
|
|
3982
|
+
const configFound = findConfigExport(sourceFile);
|
|
3983
|
+
let jsDocOwner = null;
|
|
3984
|
+
let objectLiteral = null;
|
|
3985
|
+
if (configFound) {
|
|
3986
|
+
jsDocOwner = configFound.jsDocOwner;
|
|
3987
|
+
objectLiteral = configFound.objectLiteral;
|
|
3988
|
+
} else if (pathMeta.hasRun) {
|
|
3989
|
+
const runNode = findRunExport(sourceFile);
|
|
3990
|
+
if (runNode) {
|
|
3991
|
+
jsDocOwner = runNode;
|
|
3992
|
+
}
|
|
3993
|
+
}
|
|
3994
|
+
const jsDoc = jsDocOwner ? getJSDocFromNode2(jsDocOwner) : void 0;
|
|
3995
|
+
const description = extractDescription2(jsDoc);
|
|
3996
|
+
const agentNameOverride = extractAgentTagValue(jsDoc);
|
|
3997
|
+
let systemPrompt;
|
|
3998
|
+
let tools;
|
|
3999
|
+
let agents;
|
|
4000
|
+
let model;
|
|
4001
|
+
let maxTurns;
|
|
4002
|
+
if (objectLiteral) {
|
|
4003
|
+
const fields = extractConfigFields(objectLiteral);
|
|
4004
|
+
systemPrompt = fields.systemPrompt;
|
|
4005
|
+
tools = fields.tools;
|
|
4006
|
+
agents = fields.agents;
|
|
4007
|
+
model = fields.model;
|
|
4008
|
+
maxTurns = fields.maxTurns;
|
|
4009
|
+
}
|
|
4010
|
+
return {
|
|
4011
|
+
name: agentNameOverride ?? pathMeta.name,
|
|
4012
|
+
description,
|
|
4013
|
+
filePath: pathMeta.filePath,
|
|
4014
|
+
hasConfig: pathMeta.hasConfig,
|
|
4015
|
+
hasRun: pathMeta.hasRun,
|
|
4016
|
+
systemPrompt,
|
|
4017
|
+
tools,
|
|
4018
|
+
agents,
|
|
4019
|
+
model,
|
|
4020
|
+
maxTurns
|
|
4021
|
+
};
|
|
4022
|
+
}
|
|
4023
|
+
function findConfigExport(sourceFile) {
|
|
4024
|
+
let result = null;
|
|
4025
|
+
ts8.forEachChild(sourceFile, (node) => {
|
|
4026
|
+
if (result) return;
|
|
4027
|
+
if (ts8.isVariableStatement(node) && hasExportModifier2(node)) {
|
|
4028
|
+
for (const decl of node.declarationList.declarations) {
|
|
4029
|
+
if (result) break;
|
|
4030
|
+
const nameText = ts8.isIdentifier(decl.name) ? decl.name.text : "";
|
|
4031
|
+
if (nameText !== "config" || !decl.initializer) continue;
|
|
4032
|
+
if (ts8.isObjectLiteralExpression(decl.initializer)) {
|
|
4033
|
+
result = { jsDocOwner: node, objectLiteral: decl.initializer };
|
|
4034
|
+
} else if (ts8.isArrowFunction(decl.initializer)) {
|
|
4035
|
+
const returnObj = getReturnObjectLiteral(decl.initializer);
|
|
4036
|
+
result = { jsDocOwner: node, objectLiteral: returnObj };
|
|
4037
|
+
}
|
|
4038
|
+
}
|
|
4039
|
+
}
|
|
4040
|
+
if (ts8.isFunctionDeclaration(node) && hasExportModifier2(node) && node.name?.text === "config") {
|
|
4041
|
+
const returnObj = getReturnObjectLiteral(node);
|
|
4042
|
+
result = { jsDocOwner: node, objectLiteral: returnObj };
|
|
4043
|
+
}
|
|
4044
|
+
});
|
|
4045
|
+
return result;
|
|
4046
|
+
}
|
|
4047
|
+
function findRunExport(sourceFile) {
|
|
4048
|
+
let result = null;
|
|
4049
|
+
ts8.forEachChild(sourceFile, (node) => {
|
|
4050
|
+
if (result) return;
|
|
4051
|
+
if (ts8.isFunctionDeclaration(node) && hasExportModifier2(node) && node.name?.text === "run") {
|
|
4052
|
+
result = node;
|
|
4053
|
+
return;
|
|
4054
|
+
}
|
|
4055
|
+
if (ts8.isVariableStatement(node) && hasExportModifier2(node)) {
|
|
4056
|
+
for (const decl of node.declarationList.declarations) {
|
|
4057
|
+
if (result) break;
|
|
4058
|
+
const nameText = ts8.isIdentifier(decl.name) ? decl.name.text : "";
|
|
4059
|
+
if (nameText !== "run" || !decl.initializer) continue;
|
|
4060
|
+
if (ts8.isArrowFunction(decl.initializer) || ts8.isFunctionExpression(decl.initializer)) {
|
|
4061
|
+
result = node;
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
});
|
|
4066
|
+
return result;
|
|
4067
|
+
}
|
|
4068
|
+
function getReturnObjectLiteral(fn) {
|
|
4069
|
+
const body = fn.body;
|
|
4070
|
+
if (!body) return null;
|
|
4071
|
+
if (ts8.isObjectLiteralExpression(body)) {
|
|
4072
|
+
return body;
|
|
4073
|
+
}
|
|
4074
|
+
if (ts8.isBlock(body)) {
|
|
4075
|
+
for (const stmt of body.statements) {
|
|
4076
|
+
if (ts8.isReturnStatement(stmt) && stmt.expression && ts8.isObjectLiteralExpression(stmt.expression)) {
|
|
4077
|
+
return stmt.expression;
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
4080
|
+
}
|
|
4081
|
+
return null;
|
|
4082
|
+
}
|
|
4083
|
+
function hasExportModifier2(node) {
|
|
4084
|
+
if (!ts8.canHaveModifiers(node)) return false;
|
|
4085
|
+
const modifiers = ts8.getModifiers(node);
|
|
4086
|
+
return !!modifiers?.some((m) => m.kind === ts8.SyntaxKind.ExportKeyword);
|
|
4087
|
+
}
|
|
4088
|
+
function getJSDocFromNode2(node) {
|
|
4089
|
+
const apiDocs = ts8.getJSDocCommentsAndTags(node).filter((entry) => ts8.isJSDoc(entry));
|
|
4090
|
+
if (apiDocs.length > 0) return apiDocs[0];
|
|
4091
|
+
const directDocs = node.jsDoc;
|
|
4092
|
+
if (directDocs && directDocs.length > 0) return directDocs[0];
|
|
4093
|
+
return void 0;
|
|
4094
|
+
}
|
|
4095
|
+
function extractDescription2(jsDoc) {
|
|
4096
|
+
if (!jsDoc) return void 0;
|
|
4097
|
+
if (typeof jsDoc.comment !== "string") return void 0;
|
|
4098
|
+
const trimmed = jsDoc.comment.trim();
|
|
4099
|
+
return trimmed || void 0;
|
|
4100
|
+
}
|
|
4101
|
+
function extractAgentTagValue(jsDoc) {
|
|
4102
|
+
if (!jsDoc || !jsDoc.tags) return void 0;
|
|
4103
|
+
for (const tag of jsDoc.tags) {
|
|
4104
|
+
if (tag.tagName.text !== "agent") continue;
|
|
4105
|
+
if (typeof tag.comment !== "string") return void 0;
|
|
4106
|
+
const text = tag.comment.trim();
|
|
4107
|
+
if (!text) return void 0;
|
|
4108
|
+
const cleaned = text.replace(/^\{|\}$/g, "").trim();
|
|
4109
|
+
return cleaned || void 0;
|
|
4110
|
+
}
|
|
4111
|
+
return void 0;
|
|
4112
|
+
}
|
|
4113
|
+
function extractConfigFields(objLit) {
|
|
4114
|
+
const result = {};
|
|
4115
|
+
for (const prop of objLit.properties) {
|
|
4116
|
+
if (!ts8.isPropertyAssignment(prop)) continue;
|
|
4117
|
+
const propName = getPropertyName(prop.name);
|
|
4118
|
+
if (!propName) continue;
|
|
4119
|
+
switch (propName) {
|
|
4120
|
+
case "systemPrompt":
|
|
4121
|
+
result.systemPrompt = extractStringValue(prop.initializer);
|
|
4122
|
+
break;
|
|
4123
|
+
case "tools":
|
|
4124
|
+
result.tools = extractStringArrayValue(prop.initializer);
|
|
4125
|
+
break;
|
|
4126
|
+
case "agents":
|
|
4127
|
+
result.agents = extractStringArrayValue(prop.initializer);
|
|
4128
|
+
break;
|
|
4129
|
+
case "model":
|
|
4130
|
+
result.model = extractStringValue(prop.initializer);
|
|
4131
|
+
break;
|
|
4132
|
+
case "maxTurns":
|
|
4133
|
+
result.maxTurns = extractNumberValue(prop.initializer);
|
|
4134
|
+
break;
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4137
|
+
return result;
|
|
4138
|
+
}
|
|
4139
|
+
function getPropertyName(name) {
|
|
4140
|
+
if (ts8.isIdentifier(name)) return name.text;
|
|
4141
|
+
if (ts8.isStringLiteral(name)) return name.text;
|
|
4142
|
+
return null;
|
|
4143
|
+
}
|
|
4144
|
+
function extractStringValue(expr) {
|
|
4145
|
+
if (ts8.isStringLiteral(expr)) return expr.text;
|
|
4146
|
+
return void 0;
|
|
4147
|
+
}
|
|
4148
|
+
function extractNumberValue(expr) {
|
|
4149
|
+
if (ts8.isNumericLiteral(expr)) {
|
|
4150
|
+
const num = Number(expr.text);
|
|
4151
|
+
return Number.isNaN(num) ? void 0 : num;
|
|
4152
|
+
}
|
|
4153
|
+
return void 0;
|
|
4154
|
+
}
|
|
4155
|
+
function extractStringArrayValue(expr) {
|
|
4156
|
+
if (!ts8.isArrayLiteralExpression(expr)) return void 0;
|
|
4157
|
+
const values = [];
|
|
4158
|
+
for (const element of expr.elements) {
|
|
4159
|
+
if (!ts8.isStringLiteral(element)) return void 0;
|
|
4160
|
+
values.push(element.text);
|
|
4161
|
+
}
|
|
4162
|
+
return values;
|
|
4163
|
+
}
|
|
4164
|
+
|
|
4165
|
+
// src/cli/generateAgentArtifacts.ts
|
|
4166
|
+
init_createProgram();
|
|
4167
|
+
var AGENTS_FILE = "faapi-agents.js";
|
|
4168
|
+
function toProdFilePath2(filePath, dist) {
|
|
4169
|
+
let rel = filePath.replace(/\\/g, "/");
|
|
4170
|
+
if (rel.startsWith("src/")) {
|
|
4171
|
+
rel = rel.slice(4);
|
|
4172
|
+
}
|
|
4173
|
+
const jsPath = rel.replace(/\.ts$/, ".js");
|
|
4174
|
+
return jsPath.startsWith(`${dist}/`) ? jsPath : `${dist}/${jsPath}`;
|
|
4175
|
+
}
|
|
4176
|
+
function serializeAgents(agents, dist = "dist") {
|
|
4177
|
+
return agents.map((a) => ({
|
|
4178
|
+
name: a.name,
|
|
4179
|
+
description: a.description,
|
|
4180
|
+
hasConfig: a.hasConfig,
|
|
4181
|
+
hasRun: a.hasRun,
|
|
4182
|
+
systemPrompt: a.systemPrompt,
|
|
4183
|
+
tools: a.tools,
|
|
4184
|
+
agents: a.agents,
|
|
4185
|
+
model: a.model,
|
|
4186
|
+
maxTurns: a.maxTurns,
|
|
4187
|
+
filePath: toProdFilePath2(a.filePath, dist)
|
|
4188
|
+
}));
|
|
4189
|
+
}
|
|
4190
|
+
async function writeAgentsModule(manifest, outputPath) {
|
|
4191
|
+
const dir = path13.dirname(outputPath);
|
|
4192
|
+
await fs14.mkdir(dir, { recursive: true });
|
|
4193
|
+
const content = `// \u81EA\u52A8\u751F\u6210,\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91(faapi build/dev \u4EA7\u7269)
|
|
4194
|
+
export const agents = ${JSON.stringify(manifest, null, 2)};
|
|
4195
|
+
`;
|
|
4196
|
+
await fs14.writeFile(outputPath, content, "utf-8");
|
|
4197
|
+
}
|
|
4198
|
+
function hydrateAgents(manifest) {
|
|
4199
|
+
return manifest.map((a) => ({
|
|
4200
|
+
name: a.name,
|
|
4201
|
+
description: a.description ?? void 0,
|
|
4202
|
+
filePath: a.filePath,
|
|
4203
|
+
hasConfig: a.hasConfig,
|
|
4204
|
+
hasRun: a.hasRun,
|
|
4205
|
+
systemPrompt: a.systemPrompt ?? void 0,
|
|
4206
|
+
tools: a.tools ?? void 0,
|
|
4207
|
+
agents: a.agents ?? void 0,
|
|
4208
|
+
model: a.model ?? void 0,
|
|
4209
|
+
maxTurns: a.maxTurns ?? void 0
|
|
4210
|
+
}));
|
|
4211
|
+
}
|
|
4212
|
+
async function generateAgentArtifacts(agents, rootDir, dist) {
|
|
4213
|
+
const metadata = [];
|
|
4214
|
+
for (const manifest of agents) {
|
|
4215
|
+
const absPath = path13.resolve(rootDir, manifest.filePath);
|
|
4216
|
+
const program = createProgram(absPath);
|
|
4217
|
+
const result = extractAgentMetadata(program, absPath, {
|
|
4218
|
+
name: manifest.name,
|
|
4219
|
+
filePath: manifest.filePath,
|
|
4220
|
+
hasConfig: manifest.hasConfig,
|
|
4221
|
+
hasRun: manifest.hasRun
|
|
4222
|
+
});
|
|
4223
|
+
if (result) {
|
|
4224
|
+
metadata.push(result);
|
|
4225
|
+
}
|
|
4226
|
+
}
|
|
4227
|
+
const serialized = serializeAgents(metadata, dist);
|
|
4228
|
+
const agentsPath = path13.resolve(rootDir, dist, AGENTS_FILE);
|
|
4229
|
+
await writeAgentsModule(serialized, agentsPath);
|
|
4230
|
+
return metadata;
|
|
4231
|
+
}
|
|
4232
|
+
|
|
3491
4233
|
// src/cli/loadPlugins.ts
|
|
3492
4234
|
async function loadPlugins(declarations, ctx) {
|
|
3493
4235
|
const handlerWrappers = [];
|
|
@@ -3551,7 +4293,29 @@ function resolveDeclaration(decl) {
|
|
|
3551
4293
|
var DEFAULT_DIST = "dist";
|
|
3552
4294
|
var DEFAULT_PORT = 3e3;
|
|
3553
4295
|
var ROUTES_FILE = "faapi-routes.js";
|
|
4296
|
+
var TOOLS_FILE2 = "faapi-tools.js";
|
|
4297
|
+
var AGENTS_FILE2 = "faapi-agents.js";
|
|
3554
4298
|
var PATTERNS = ["src/api/**/*.ts"];
|
|
4299
|
+
async function loadAndHydrateTools(rootDir, dist) {
|
|
4300
|
+
const toolsPath = path14.resolve(rootDir, dist, TOOLS_FILE2);
|
|
4301
|
+
if (!fs15.existsSync(toolsPath)) {
|
|
4302
|
+
return [];
|
|
4303
|
+
}
|
|
4304
|
+
const serialized = await importWithCacheBust(toolsPath);
|
|
4305
|
+
const hydrated = hydrateTools(serialized.tools ?? []);
|
|
4306
|
+
hydrateToolRegistry(hydrated);
|
|
4307
|
+
return hydrated;
|
|
4308
|
+
}
|
|
4309
|
+
async function loadAndHydrateAgents(rootDir, dist) {
|
|
4310
|
+
const agentsPath = path14.resolve(rootDir, dist, AGENTS_FILE2);
|
|
4311
|
+
if (!fs15.existsSync(agentsPath)) {
|
|
4312
|
+
return [];
|
|
4313
|
+
}
|
|
4314
|
+
const serialized = await importWithCacheBust(agentsPath);
|
|
4315
|
+
const hydrated = hydrateAgents(serialized.agents ?? []);
|
|
4316
|
+
hydrateAgentRegistry(hydrated);
|
|
4317
|
+
return hydrated;
|
|
4318
|
+
}
|
|
3555
4319
|
var APP_INSTANCE_KEY = /* @__PURE__ */ Symbol.for("faapi.app.instance");
|
|
3556
4320
|
function getCurrentApp() {
|
|
3557
4321
|
return globalThis[APP_INSTANCE_KEY] ?? null;
|
|
@@ -3591,8 +4355,8 @@ function isFaapiConfigKey(key) {
|
|
|
3591
4355
|
async function createAppBase(options) {
|
|
3592
4356
|
const rootDir = options?.rootDir ?? process.cwd();
|
|
3593
4357
|
const dist = options?.dist ?? process.env.FAAPI_DIST ?? DEFAULT_DIST;
|
|
3594
|
-
const routesPath =
|
|
3595
|
-
if (!
|
|
4358
|
+
const routesPath = path14.resolve(rootDir, dist, ROUTES_FILE);
|
|
4359
|
+
if (!fs15.existsSync(routesPath)) {
|
|
3596
4360
|
throw new Error(
|
|
3597
4361
|
`[faapi] ${dist}/${ROUTES_FILE} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6267\u884C \`faapi build\`\uFF08\u6216 \`faapi dev\`\uFF09\u751F\u6210\u4EA7\u7269\u3002`
|
|
3598
4362
|
);
|
|
@@ -3611,6 +4375,8 @@ async function createAppBase(options) {
|
|
|
3611
4375
|
}
|
|
3612
4376
|
}
|
|
3613
4377
|
}
|
|
4378
|
+
const tools = await loadAndHydrateTools(rootDir, dist);
|
|
4379
|
+
const agents = await loadAndHydrateAgents(rootDir, dist);
|
|
3614
4380
|
const pluginConfig = config ? Object.fromEntries(Object.entries(config).filter(([k]) => !isFaapiConfigKey(k))) : {};
|
|
3615
4381
|
const { server, routesRef } = createServer({
|
|
3616
4382
|
routes: sorted,
|
|
@@ -3660,6 +4426,21 @@ async function createAppBase(options) {
|
|
|
3660
4426
|
console.log(` WS ${route.urlPath} ${route.filePath}`);
|
|
3661
4427
|
}
|
|
3662
4428
|
}
|
|
4429
|
+
if (tools.length > 0) {
|
|
4430
|
+
console.log(`- Loaded ${tools.length} tool(s):`);
|
|
4431
|
+
for (const tool of tools) {
|
|
4432
|
+
console.log(` ${tool.name} ${tool.filePath}`);
|
|
4433
|
+
}
|
|
4434
|
+
}
|
|
4435
|
+
if (agents.length > 0) {
|
|
4436
|
+
console.log(`- Loaded ${agents.length} agent(s):`);
|
|
4437
|
+
for (const agent of agents) {
|
|
4438
|
+
const exports = [];
|
|
4439
|
+
if (agent.hasConfig) exports.push("config");
|
|
4440
|
+
if (agent.hasRun) exports.push("run");
|
|
4441
|
+
console.log(` ${agent.name} [${exports.join("+")}] ${agent.filePath}`);
|
|
4442
|
+
}
|
|
4443
|
+
}
|
|
3663
4444
|
if (config?.lifecycle?.onClose) {
|
|
3664
4445
|
const graceful = async (signal) => {
|
|
3665
4446
|
console.log(`
|
|
@@ -3757,6 +4538,9 @@ async function createAppBase(options) {
|
|
|
3757
4538
|
if (config?.lifecycle?.onClose) {
|
|
3758
4539
|
await config.lifecycle.onClose({ rootDir, routes: sorted, server });
|
|
3759
4540
|
}
|
|
4541
|
+
clearToolRegistry();
|
|
4542
|
+
clearAgentRegistry();
|
|
4543
|
+
clearAgentHandleFactory();
|
|
3760
4544
|
if (!server.listening) {
|
|
3761
4545
|
app.server = null;
|
|
3762
4546
|
if (getCurrentApp() === app) setCurrentApp(null);
|
|
@@ -3794,17 +4578,17 @@ async function createAppBase(options) {
|
|
|
3794
4578
|
|
|
3795
4579
|
// src/router/scanRoutes.ts
|
|
3796
4580
|
import fg2 from "fast-glob";
|
|
3797
|
-
import
|
|
3798
|
-
import
|
|
4581
|
+
import path15 from "path";
|
|
4582
|
+
import fs16 from "fs";
|
|
3799
4583
|
|
|
3800
4584
|
// src/router/constants.ts
|
|
3801
4585
|
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
3802
4586
|
var HTTP_METHOD_SET = new Set(HTTP_METHODS);
|
|
3803
4587
|
|
|
3804
4588
|
// src/utils/normalizePath.ts
|
|
3805
|
-
function normalizePath(
|
|
3806
|
-
if (!
|
|
3807
|
-
let result =
|
|
4589
|
+
function normalizePath(path18) {
|
|
4590
|
+
if (!path18) return "";
|
|
4591
|
+
let result = path18.replace(/\\/g, "/");
|
|
3808
4592
|
result = result.replace(/\/+/g, "/");
|
|
3809
4593
|
result = result.replace(/\/+$/, "");
|
|
3810
4594
|
if (result && !result.startsWith("/")) {
|
|
@@ -3865,41 +4649,41 @@ function extractExportsFromSource(source) {
|
|
|
3865
4649
|
return names;
|
|
3866
4650
|
}
|
|
3867
4651
|
function collectMiddlewarePaths(routeFilePath, rootDir, dist) {
|
|
3868
|
-
const routeDir =
|
|
3869
|
-
const resolvedRoot =
|
|
4652
|
+
const routeDir = path15.dirname(routeFilePath);
|
|
4653
|
+
const resolvedRoot = path15.resolve(rootDir);
|
|
3870
4654
|
const paths = [];
|
|
3871
|
-
let currentDir =
|
|
4655
|
+
let currentDir = path15.resolve(rootDir, routeDir);
|
|
3872
4656
|
while (true) {
|
|
3873
4657
|
if (dist) {
|
|
3874
|
-
const mwTsPath =
|
|
3875
|
-
const mwJsPath =
|
|
3876
|
-
const absTsPath =
|
|
3877
|
-
const absJsPath =
|
|
3878
|
-
const absMwPath =
|
|
4658
|
+
const mwTsPath = path15.join(currentDir, "middlewares.ts");
|
|
4659
|
+
const mwJsPath = path15.join(currentDir, "middlewares.js");
|
|
4660
|
+
const absTsPath = path15.resolve(rootDir, mwTsPath);
|
|
4661
|
+
const absJsPath = path15.resolve(rootDir, mwJsPath);
|
|
4662
|
+
const absMwPath = fs16.existsSync(absTsPath) ? absTsPath : fs16.existsSync(absJsPath) ? absJsPath : null;
|
|
3879
4663
|
if (absMwPath) {
|
|
3880
|
-
const relMwPath =
|
|
3881
|
-
const prodAbsPath =
|
|
4664
|
+
const relMwPath = path15.relative(rootDir, absMwPath);
|
|
4665
|
+
const prodAbsPath = path15.resolve(rootDir, toProdFilePath3(relMwPath, dist));
|
|
3882
4666
|
paths.push(prodAbsPath);
|
|
3883
4667
|
}
|
|
3884
4668
|
} else {
|
|
3885
4669
|
for (const ext of [".ts", ".js"]) {
|
|
3886
|
-
const mwPath =
|
|
3887
|
-
const absMwPath =
|
|
3888
|
-
if (
|
|
4670
|
+
const mwPath = path15.join(currentDir, `middlewares${ext}`);
|
|
4671
|
+
const absMwPath = path15.resolve(rootDir, mwPath);
|
|
4672
|
+
if (fs16.existsSync(absMwPath)) {
|
|
3889
4673
|
paths.push(absMwPath);
|
|
3890
4674
|
break;
|
|
3891
4675
|
}
|
|
3892
4676
|
}
|
|
3893
4677
|
}
|
|
3894
4678
|
if (currentDir === resolvedRoot) break;
|
|
3895
|
-
const parentDir =
|
|
4679
|
+
const parentDir = path15.dirname(currentDir);
|
|
3896
4680
|
if (parentDir === currentDir) break;
|
|
3897
4681
|
currentDir = parentDir;
|
|
3898
4682
|
}
|
|
3899
4683
|
paths.reverse();
|
|
3900
4684
|
return paths;
|
|
3901
4685
|
}
|
|
3902
|
-
function
|
|
4686
|
+
function toProdFilePath3(filePath, dist) {
|
|
3903
4687
|
let rel = filePath.replace(/\\/g, "/");
|
|
3904
4688
|
if (rel.startsWith("src/")) {
|
|
3905
4689
|
rel = rel.slice(4);
|
|
@@ -3919,7 +4703,7 @@ async function scanRoutes(rootDir, patterns, dist) {
|
|
|
3919
4703
|
const normalizedFile = file.replace(/\\/g, "/");
|
|
3920
4704
|
const fileName = normalizedFile.split("/").pop();
|
|
3921
4705
|
if (fileName === "handler.ts" || fileName === "handler.js") {
|
|
3922
|
-
const absPath =
|
|
4706
|
+
const absPath = path15.resolve(rootDir, normalizedFile);
|
|
3923
4707
|
const urlPath = filePathToUrlPath(normalizedFile);
|
|
3924
4708
|
const paramNames = extractParamNames(urlPath);
|
|
3925
4709
|
const isDynamic = paramNames.length > 0;
|
|
@@ -3932,7 +4716,7 @@ async function scanRoutes(rootDir, patterns, dist) {
|
|
|
3932
4716
|
const mwPaths = collectMiddlewarePaths(normalizedFile, rootDir);
|
|
3933
4717
|
middlewareBundle = await loadMergedMiddlewares(mwPaths);
|
|
3934
4718
|
}
|
|
3935
|
-
const source = await
|
|
4719
|
+
const source = await fs16.promises.readFile(absPath, "utf8").catch(() => "");
|
|
3936
4720
|
const exportNames = extractExportsFromSource(source);
|
|
3937
4721
|
const methods = HTTP_METHODS.filter((m) => exportNames.has(m));
|
|
3938
4722
|
for (const method of methods) {
|
|
@@ -3966,6 +4750,140 @@ async function scanRoutes(rootDir, patterns, dist) {
|
|
|
3966
4750
|
return { routes, wsRoutes };
|
|
3967
4751
|
}
|
|
3968
4752
|
|
|
4753
|
+
// src/tools/scanTools.ts
|
|
4754
|
+
import fg3 from "fast-glob";
|
|
4755
|
+
import path16 from "path";
|
|
4756
|
+
import fs17 from "fs";
|
|
4757
|
+
var TOOL_PATTERNS = ["src/tools/**/*.ts"];
|
|
4758
|
+
var TOOL_EXPORT_RE = new RegExp(
|
|
4759
|
+
String.raw`export\s+(?:async\s+)?(?:function\s+|const\s+)([A-Za-z_$][\w$]*)\s*(?:\(|=)`,
|
|
4760
|
+
"g"
|
|
4761
|
+
);
|
|
4762
|
+
var RESERVED_EXPORTS = /* @__PURE__ */ new Set(["default", "config", "run"]);
|
|
4763
|
+
function extractToolExportsFromSource(source) {
|
|
4764
|
+
const names = /* @__PURE__ */ new Set();
|
|
4765
|
+
let match;
|
|
4766
|
+
TOOL_EXPORT_RE.lastIndex = 0;
|
|
4767
|
+
while ((match = TOOL_EXPORT_RE.exec(source)) !== null) {
|
|
4768
|
+
const name = match[1];
|
|
4769
|
+
if (!RESERVED_EXPORTS.has(name)) {
|
|
4770
|
+
names.add(name);
|
|
4771
|
+
}
|
|
4772
|
+
}
|
|
4773
|
+
return names;
|
|
4774
|
+
}
|
|
4775
|
+
function extractNamespaceFromRelPath(relPath) {
|
|
4776
|
+
const lastSlash = relPath.lastIndexOf("/");
|
|
4777
|
+
const dirPath = lastSlash === -1 ? "" : relPath.slice(0, lastSlash);
|
|
4778
|
+
if (!dirPath) return "";
|
|
4779
|
+
return dirPath.split("/").join(".");
|
|
4780
|
+
}
|
|
4781
|
+
function filePathToToolNamespace(filePath) {
|
|
4782
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
4783
|
+
const toolsMatch = normalized.match(/(?:^|\/)tools\/(.+)$/);
|
|
4784
|
+
if (toolsMatch) {
|
|
4785
|
+
return extractNamespaceFromRelPath(toolsMatch[1]);
|
|
4786
|
+
}
|
|
4787
|
+
return "";
|
|
4788
|
+
}
|
|
4789
|
+
function buildToolName(namespace, functionName) {
|
|
4790
|
+
return namespace ? `${namespace}.${functionName}` : functionName;
|
|
4791
|
+
}
|
|
4792
|
+
async function scanTools(rootDir, patterns) {
|
|
4793
|
+
const files = await fg3(patterns, {
|
|
4794
|
+
cwd: rootDir,
|
|
4795
|
+
onlyFiles: true,
|
|
4796
|
+
absolute: false
|
|
4797
|
+
});
|
|
4798
|
+
const tools = [];
|
|
4799
|
+
const seen = /* @__PURE__ */ new Map();
|
|
4800
|
+
for (const file of files) {
|
|
4801
|
+
const normalizedFile = file.replace(/\\/g, "/");
|
|
4802
|
+
const fileName = normalizedFile.split("/").pop();
|
|
4803
|
+
if (fileName !== "handler.ts" && fileName !== "handler.js") {
|
|
4804
|
+
continue;
|
|
4805
|
+
}
|
|
4806
|
+
const absPath = path16.resolve(rootDir, normalizedFile);
|
|
4807
|
+
const source = await fs17.promises.readFile(absPath, "utf8").catch(() => "");
|
|
4808
|
+
const exportNames = extractToolExportsFromSource(source);
|
|
4809
|
+
const namespace = filePathToToolNamespace(normalizedFile);
|
|
4810
|
+
for (const fnName of exportNames) {
|
|
4811
|
+
const toolName = buildToolName(namespace, fnName);
|
|
4812
|
+
const prevFile = seen.get(toolName);
|
|
4813
|
+
if (prevFile) {
|
|
4814
|
+
throw new Error(
|
|
4815
|
+
`Tool conflict: "${toolName}" declared in both ${prevFile} and ${normalizedFile}`
|
|
4816
|
+
);
|
|
4817
|
+
}
|
|
4818
|
+
seen.set(toolName, normalizedFile);
|
|
4819
|
+
tools.push({
|
|
4820
|
+
name: toolName,
|
|
4821
|
+
functionName: fnName,
|
|
4822
|
+
filePath: normalizedFile
|
|
4823
|
+
});
|
|
4824
|
+
}
|
|
4825
|
+
}
|
|
4826
|
+
return tools;
|
|
4827
|
+
}
|
|
4828
|
+
|
|
4829
|
+
// src/agents/scanAgents.ts
|
|
4830
|
+
import fg4 from "fast-glob";
|
|
4831
|
+
import path17 from "path";
|
|
4832
|
+
import fs18 from "fs";
|
|
4833
|
+
var DEFAULT_AGENT_PATTERNS = ["src/agents/*/handler.ts"];
|
|
4834
|
+
var CONFIG_EXPORT_RE = /export\s+(?:const|function)\s+config\b/;
|
|
4835
|
+
var RUN_EXPORT_RE = /export\s+(?:async\s+)?(?:function\s+|const\s+)run\b/;
|
|
4836
|
+
function extractAgentNameFromPath(filePath) {
|
|
4837
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
4838
|
+
const match = normalized.match(/(?:^|\/)agents\/([^/]+)\/handler\.ts$/);
|
|
4839
|
+
if (!match) {
|
|
4840
|
+
throw new Error(
|
|
4841
|
+
`Not an agent path: "${filePath}". Expected pattern: src/agents/<name>/handler.ts`
|
|
4842
|
+
);
|
|
4843
|
+
}
|
|
4844
|
+
return match[1];
|
|
4845
|
+
}
|
|
4846
|
+
function detectAgentExports(source) {
|
|
4847
|
+
return {
|
|
4848
|
+
hasConfig: CONFIG_EXPORT_RE.test(source),
|
|
4849
|
+
hasRun: RUN_EXPORT_RE.test(source)
|
|
4850
|
+
};
|
|
4851
|
+
}
|
|
4852
|
+
async function scanAgents(rootDir, patterns) {
|
|
4853
|
+
const files = await fg4(patterns, {
|
|
4854
|
+
cwd: rootDir,
|
|
4855
|
+
onlyFiles: true,
|
|
4856
|
+
absolute: false
|
|
4857
|
+
});
|
|
4858
|
+
const agents = [];
|
|
4859
|
+
const seen = /* @__PURE__ */ new Map();
|
|
4860
|
+
for (const file of files) {
|
|
4861
|
+
const normalizedFile = file.replace(/\\/g, "/");
|
|
4862
|
+
const fileName = normalizedFile.split("/").pop();
|
|
4863
|
+
if (fileName !== "handler.ts" && fileName !== "handler.js") {
|
|
4864
|
+
continue;
|
|
4865
|
+
}
|
|
4866
|
+
const absPath = path17.resolve(rootDir, normalizedFile);
|
|
4867
|
+
const source = await fs18.promises.readFile(absPath, "utf8").catch(() => "");
|
|
4868
|
+
const { hasConfig, hasRun } = detectAgentExports(source);
|
|
4869
|
+
const name = extractAgentNameFromPath(normalizedFile);
|
|
4870
|
+
const prevFile = seen.get(name);
|
|
4871
|
+
if (prevFile) {
|
|
4872
|
+
throw new Error(
|
|
4873
|
+
`Agent conflict: "${name}" declared in both ${prevFile} and ${normalizedFile}`
|
|
4874
|
+
);
|
|
4875
|
+
}
|
|
4876
|
+
seen.set(name, normalizedFile);
|
|
4877
|
+
agents.push({
|
|
4878
|
+
name,
|
|
4879
|
+
filePath: normalizedFile,
|
|
4880
|
+
hasConfig,
|
|
4881
|
+
hasRun
|
|
4882
|
+
});
|
|
4883
|
+
}
|
|
4884
|
+
return agents;
|
|
4885
|
+
}
|
|
4886
|
+
|
|
3969
4887
|
// src/cli/createDevApp.ts
|
|
3970
4888
|
init_createProgram();
|
|
3971
4889
|
async function createDevApp(options) {
|
|
@@ -3988,6 +4906,22 @@ async function createDevApp(options) {
|
|
|
3988
4906
|
}
|
|
3989
4907
|
ctx.updateRoutes(sorted, reScanned.wsRoutes);
|
|
3990
4908
|
};
|
|
4909
|
+
devApp.reloadTools = async () => {
|
|
4910
|
+
setLoadTimestamp(Date.now());
|
|
4911
|
+
invalidateProgramCache();
|
|
4912
|
+
const tools = await scanTools(ctx.rootDir, TOOL_PATTERNS);
|
|
4913
|
+
await generateToolArtifacts(tools, ctx.rootDir, ctx.dist, {
|
|
4914
|
+
skipSchema: isDevOnDemandEnabled()
|
|
4915
|
+
});
|
|
4916
|
+
await loadAndHydrateTools(ctx.rootDir, ctx.dist);
|
|
4917
|
+
};
|
|
4918
|
+
devApp.reloadAgents = async () => {
|
|
4919
|
+
setLoadTimestamp(Date.now());
|
|
4920
|
+
invalidateProgramCache();
|
|
4921
|
+
const agents = await scanAgents(ctx.rootDir, DEFAULT_AGENT_PATTERNS);
|
|
4922
|
+
await generateAgentArtifacts(agents, ctx.rootDir, ctx.dist);
|
|
4923
|
+
await loadAndHydrateAgents(ctx.rootDir, ctx.dist);
|
|
4924
|
+
};
|
|
3991
4925
|
return devApp;
|
|
3992
4926
|
}
|
|
3993
4927
|
|
|
@@ -4004,6 +4938,7 @@ export {
|
|
|
4004
4938
|
RouteNotFoundError,
|
|
4005
4939
|
SchemaExtractionError,
|
|
4006
4940
|
ValidationError,
|
|
4941
|
+
clearAgentHandleFactory,
|
|
4007
4942
|
collectRouteSchemaSources,
|
|
4008
4943
|
cors,
|
|
4009
4944
|
createProdApp as createApp,
|
|
@@ -4011,13 +4946,21 @@ export {
|
|
|
4011
4946
|
createProdApp,
|
|
4012
4947
|
createProgram,
|
|
4013
4948
|
extractTypeInfo,
|
|
4949
|
+
getAgent,
|
|
4014
4950
|
getApp,
|
|
4015
4951
|
getInputTypeForMethod,
|
|
4952
|
+
getTool,
|
|
4016
4953
|
helmet,
|
|
4017
4954
|
invalidateProgramCache,
|
|
4955
|
+
loadAgentModule,
|
|
4018
4956
|
loadConfig,
|
|
4019
4957
|
loadEnv,
|
|
4958
|
+
loadToolModule,
|
|
4959
|
+
loadToolSchema,
|
|
4020
4960
|
logger,
|
|
4961
|
+
registerAgentHandleFactory,
|
|
4962
|
+
resolveAgentTools,
|
|
4963
|
+
resolveSubAgents,
|
|
4021
4964
|
resolveTypeNode
|
|
4022
4965
|
};
|
|
4023
4966
|
//# sourceMappingURL=index.js.map
|