@faapi/faapi 2.0.1 → 3.1.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/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 path7 from "path";
1223
- import fs6 from "fs/promises";
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 path7.resolve(rootDir, dist, relDir, "zod.js");
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 path7.resolve(rootDir, dist, relDir, "zod.js");
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 = path7.relative(rootDir, filePath).replace(/\\/g, "/");
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 = path7.resolve(rootDir, dist, HELPERS_FILENAME);
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 fs6.mkdir(path7.dirname(outputPath), { recursive: true });
1316
- await fs6.writeFile(outputPath, source, "utf-8");
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,117 @@ init_resolveTypeNode();
1330
1334
  init_inputType();
1331
1335
  init_collectRouteSchemaSources();
1332
1336
 
1333
- // src/middleware/cors.ts
1334
- var DEFAULT_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
1335
- function cors(options = {}) {
1336
- const {
1337
- origin = true,
1338
- methods = DEFAULT_METHODS,
1339
- allowedHeaders,
1340
- exposeHeaders,
1341
- credentials = false,
1342
- maxAge
1343
- } = options;
1344
- return async (ctx, next) => {
1345
- const reqOrigin = ctx.headers.get("origin");
1346
- if (!reqOrigin) {
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/middleware/logger.ts
1399
- function logger(options = {}) {
1400
- return async (ctx, next) => {
1401
- const log = options.log ?? console.log;
1402
- const start = Date.now();
1403
- try {
1404
- const response = await next();
1405
- const duration = Date.now() - start;
1406
- const entry = {
1407
- method: ctx.method,
1408
- path: ctx.path,
1409
- status: response.status,
1410
- durationMs: duration
1411
- };
1412
- log(entry, `${ctx.method} ${ctx.path} ${response.status} ${duration}ms`);
1413
- return response;
1414
- } catch (err) {
1415
- const duration = Date.now() - start;
1416
- const message = err instanceof Error ? err.message : String(err);
1417
- const status = err?.statusCode ?? 500;
1418
- const entry = {
1419
- method: ctx.method,
1420
- path: ctx.path,
1421
- status,
1422
- durationMs: duration,
1423
- error: message
1424
- };
1425
- log(entry, `${ctx.method} ${ctx.path} ${status} ${duration}ms - ${message}`);
1426
- throw err;
1427
- }
1428
- };
1353
+ // src/injection/skillRegistry.ts
1354
+ var registry2 = /* @__PURE__ */ new Map();
1355
+ function hydrateSkillRegistry(skills) {
1356
+ const next = /* @__PURE__ */ new Map();
1357
+ for (const skill of skills) {
1358
+ next.set(skill.name, skill);
1359
+ }
1360
+ registry2 = next;
1361
+ }
1362
+ function clearSkillRegistry() {
1363
+ registry2 = /* @__PURE__ */ new Map();
1364
+ }
1365
+ function upsertSkill(core) {
1366
+ registry2.set(core.name, core);
1367
+ }
1368
+ function removeSkill(name) {
1369
+ registry2.delete(name);
1370
+ }
1371
+ function getSkill(name) {
1372
+ return registry2.get(name);
1373
+ }
1374
+ function listSkills() {
1375
+ return Array.from(registry2.values());
1429
1376
  }
1430
1377
 
1431
- // src/middleware/helmet.ts
1432
- var DEFAULTS = {
1433
- contentSecurityPolicy: "default-src 'self'",
1434
- xFrameOptions: "SAMEORIGIN",
1435
- xContentTypeOptions: true,
1436
- referrerPolicy: "no-referrer",
1437
- strictTransportSecurity: "max-age=31536000; includeSubDomains",
1438
- xDnsPrefetchControl: true,
1439
- xDownloadOptions: true,
1440
- xPermittedCrossDomainPolicies: "none",
1441
- crossOriginOpenerPolicy: "same-origin",
1442
- crossOriginResourcePolicy: "same-origin",
1443
- crossOriginEmbedderPolicy: false,
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");
1378
+ // src/injection/agentRegistry.ts
1379
+ var registry3 = /* @__PURE__ */ new Map();
1380
+ function hydrateAgentRegistry(agents) {
1381
+ const next = /* @__PURE__ */ new Map();
1382
+ for (const agent of agents) {
1383
+ next.set(agent.name, agent);
1384
+ }
1385
+ registry3 = next;
1386
+ }
1387
+ function clearAgentRegistry() {
1388
+ registry3 = /* @__PURE__ */ new Map();
1389
+ }
1390
+ function getAgent(name) {
1391
+ return getSkill(name) ?? registry3.get(name);
1392
+ }
1393
+ function getAgentEntry(name) {
1394
+ return registry3.get(name);
1395
+ }
1396
+ function listAgents() {
1397
+ const merged = /* @__PURE__ */ new Map();
1398
+ for (const agent of registry3.values()) merged.set(agent.name, agent);
1399
+ for (const skill of listSkills()) merged.set(skill.name, skill);
1400
+ return Array.from(merged.values());
1401
+ }
1402
+ function resolveAgentTools(name) {
1403
+ const agent = getAgent(name);
1404
+ if (!agent) return [];
1405
+ const result = /* @__PURE__ */ new Map();
1406
+ if (agent.tools) {
1407
+ for (const toolName of agent.tools) {
1408
+ const tool = getTool(toolName);
1409
+ if (tool) result.set(tool.name, tool);
1488
1410
  }
1489
- return await next();
1490
- };
1411
+ }
1412
+ return Array.from(result.values());
1413
+ }
1414
+ function resolveSubAgents(name) {
1415
+ const agent = getAgent(name);
1416
+ if (!agent || !agent.agents) return [];
1417
+ const result = [];
1418
+ for (const agentName of agent.agents) {
1419
+ const subAgent = getAgent(agentName);
1420
+ if (subAgent) result.push(subAgent);
1421
+ }
1422
+ return result;
1491
1423
  }
1492
1424
 
1493
- // src/config/loadConfig.ts
1494
- import path2 from "path";
1495
- import fs from "fs";
1425
+ // src/loader/loadAgentModule.ts
1426
+ import fs6 from "fs";
1427
+
1428
+ // src/loader/resolveExports.ts
1429
+ function resolveExport(module, exportName) {
1430
+ if (exportName in module && typeof module[exportName] !== "undefined") {
1431
+ return module[exportName];
1432
+ }
1433
+ const defaultExport = module.default;
1434
+ if (defaultExport !== null && typeof defaultExport === "object") {
1435
+ const value = defaultExport[exportName];
1436
+ if (value !== void 0) {
1437
+ return value;
1438
+ }
1439
+ }
1440
+ return void 0;
1441
+ }
1496
1442
 
1497
1443
  // src/utils/importWithCacheBust.ts
1498
1444
  import { pathToFileURL } from "url";
1499
1445
  var loadTs;
1500
- function setLoadTimestamp(ts7) {
1501
- loadTs = ts7;
1446
+ function setLoadTimestamp(ts9) {
1447
+ loadTs = ts9;
1502
1448
  }
1503
1449
  function getVitestImportActual() {
1504
1450
  const vi = globalThis.vi;
@@ -1522,666 +1468,1246 @@ async function importWithCacheBust(filePath, bustViteCache = false) {
1522
1468
  return await import(url);
1523
1469
  }
1524
1470
 
1525
- // src/config/loadConfig.ts
1526
- var CONFIG_PRODUCT_FILE = "faapi-config.js";
1527
- async function loadConfig(rootDir, dist) {
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 ?? {};
1471
+ // src/cli/compileOnDemand.ts
1472
+ import path6 from "path";
1473
+ import fs5 from "fs";
1474
+
1475
+ // src/cli/compileDevRoutes.ts
1476
+ import path4 from "path";
1477
+ import fs3 from "fs";
1478
+ import fg from "fast-glob";
1479
+
1480
+ // src/cli/aliasPlugin.ts
1481
+ import path3 from "path";
1482
+ import fs2 from "fs";
1483
+
1484
+ // src/utils/resolveAlias.ts
1485
+ function resolveAlias(specifier, config) {
1486
+ const candidates = [];
1487
+ for (const [pattern, targets] of Object.entries(config.paths)) {
1488
+ const wildcardIndex = pattern.indexOf("*");
1489
+ if (wildcardIndex === -1) {
1490
+ if (specifier === pattern) {
1491
+ candidates.push(...targets);
1492
+ }
1493
+ continue;
1494
+ }
1495
+ const prefix = pattern.slice(0, wildcardIndex);
1496
+ const suffix = pattern.slice(wildcardIndex + 1);
1497
+ if (specifier.startsWith(prefix) && specifier.endsWith(suffix) && specifier.length >= prefix.length + suffix.length) {
1498
+ const captured = specifier.slice(prefix.length, specifier.length - suffix.length);
1499
+ for (const target of targets) {
1500
+ candidates.push(target.replace("*", captured));
1501
+ }
1502
+ }
1532
1503
  }
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
- );
1504
+ return candidates;
1505
+ }
1506
+
1507
+ // src/utils/readTsconfig.ts
1508
+ import ts6 from "typescript";
1509
+ import path2 from "path";
1510
+ import fs from "fs";
1511
+ function readTsconfig(rootDir) {
1512
+ const tsconfigPath = path2.resolve(rootDir, "tsconfig.json");
1513
+ if (!fs.existsSync(tsconfigPath)) return null;
1514
+ const configFile = ts6.readConfigFile(tsconfigPath, ts6.sys.readFile);
1515
+ if (configFile.error || !configFile.config) return null;
1516
+ const parsed = ts6.parseJsonConfigFileContent(configFile.config, ts6.sys, rootDir);
1517
+ const baseUrl = parsed.options.baseUrl ?? rootDir;
1518
+ const rawPaths = parsed.options.paths;
1519
+ if (!rawPaths) return null;
1520
+ const paths = {};
1521
+ for (const [pattern, targets] of Object.entries(rawPaths)) {
1522
+ paths[pattern] = targets.map((t) => path2.resolve(baseUrl, t));
1538
1523
  }
1539
- return null;
1524
+ return { baseUrl, paths };
1540
1525
  }
1541
1526
 
1542
- // src/cli/loadEnv.ts
1543
- import fs2 from "fs";
1544
- import path3 from "path";
1545
- function resolveEnv() {
1546
- return process.env.NODE_ENV || "development";
1527
+ // src/cli/aliasPlugin.ts
1528
+ function toProdExtension(filePath) {
1529
+ if (filePath.endsWith(".ts")) return filePath.slice(0, -3) + ".js";
1530
+ if (filePath.endsWith(".tsx")) return filePath.slice(0, -4) + ".js";
1531
+ if (filePath.endsWith(".jsx")) return filePath.slice(0, -4) + ".js";
1532
+ return filePath;
1547
1533
  }
1548
- function getEnvFiles(env) {
1549
- return [".env", ".env.local", `.env.${env}`, `.env.${env}.local`];
1534
+ function toProdImportPath(sourceFile, importer) {
1535
+ const importerDir = path3.dirname(importer);
1536
+ let rel = path3.relative(importerDir, sourceFile);
1537
+ rel = rel.split(path3.sep).join("/");
1538
+ if (!rel.startsWith(".")) rel = "./" + rel;
1539
+ return toProdExtension(rel);
1550
1540
  }
1551
- function parseEnvFile(content, fileVars) {
1552
- const result = {};
1553
- const lines = content.replace(/\r\n/g, "\n").split("\n");
1554
- for (const line of lines) {
1555
- const trimmed = line.trim();
1556
- if (!trimmed || trimmed.startsWith("#")) continue;
1557
- const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(trimmed);
1558
- if (!match) continue;
1559
- const [, key, rawValue] = match;
1560
- const value = parseValue(rawValue, { ...fileVars, ...result });
1561
- result[key] = value;
1541
+ function toRealPath(p) {
1542
+ try {
1543
+ return fs2.realpathSync(p);
1544
+ } catch {
1545
+ return p;
1562
1546
  }
1563
- return result;
1564
1547
  }
1565
- function parseValue(raw, env) {
1566
- if (raw === "") return "";
1567
- if (raw[0] === "'") {
1568
- const end = raw.indexOf("'", 1);
1569
- return end === -1 ? raw.slice(1) : raw.slice(1, end);
1548
+ function isInsideDir(filePath, dir) {
1549
+ const rel = path3.relative(dir, filePath);
1550
+ return rel !== "" && !rel.startsWith("..") && !path3.isAbsolute(rel);
1551
+ }
1552
+ var APP_DIR = "src";
1553
+ function toStrippedProdImportPath(sourceFile, rootDir) {
1554
+ const appDirAbs = toRealPath(path3.resolve(rootDir, APP_DIR));
1555
+ const sourceReal = toRealPath(sourceFile);
1556
+ let rel = path3.relative(appDirAbs, sourceReal);
1557
+ rel = rel.split(path3.sep).join("/");
1558
+ if (!rel.startsWith(".")) rel = "./" + rel;
1559
+ return toProdExtension(rel);
1560
+ }
1561
+ var PROD_EXTS = [".js", ".mjs", ".cjs"];
1562
+ var SOURCE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
1563
+ var INDEX_EXTS = [
1564
+ "/index.ts",
1565
+ "/index.tsx",
1566
+ "/index.js",
1567
+ "/index.jsx",
1568
+ "/index.mjs",
1569
+ "/index.cjs"
1570
+ ];
1571
+ function resolveRelativeSpecifier(importer, specifier) {
1572
+ const importerDir = path3.dirname(importer);
1573
+ const base = path3.resolve(importerDir, specifier);
1574
+ if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
1575
+ return fs2.existsSync(base) ? base : null;
1570
1576
  }
1571
- if (raw[0] === '"') {
1572
- const match = /^"((?:\\.|[^"\\])*)"/.exec(raw);
1573
- const inner = match ? match[1] : raw.slice(1);
1574
- return expandEscapesAndVars(inner, env);
1577
+ if (/\.(ts|tsx|jsx)$/.test(specifier)) {
1578
+ return fs2.existsSync(base) ? base : null;
1575
1579
  }
1576
- const commentMatch = /^(.*?)(\s+#.*)$/.exec(raw);
1577
- const value = commentMatch ? commentMatch[1] : raw;
1578
- return value.trim();
1580
+ for (const ext of SOURCE_EXTS) {
1581
+ const file = base + ext;
1582
+ if (fs2.existsSync(file)) return file;
1583
+ }
1584
+ for (const indexExt of INDEX_EXTS) {
1585
+ const file = base + indexExt;
1586
+ if (fs2.existsSync(file)) return file;
1587
+ }
1588
+ return null;
1579
1589
  }
1580
- function expandEscapesAndVars(str, env) {
1581
- const escaped = str.replace(/\\(.)/g, (_, ch) => {
1582
- switch (ch) {
1583
- case "n":
1584
- return "\n";
1585
- case "r":
1586
- return "\r";
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] ?? "";
1590
+ function createAliasPlugin(config, options) {
1591
+ const SPEC_RE = /(\bfrom\s*|import\s*\(\s*)(['"])([^'"]+)\2/g;
1592
+ const appDirAbs = options?.rootDir ? toRealPath(path3.resolve(options.rootDir, APP_DIR)) : null;
1593
+ return {
1594
+ name: "faapi-alias",
1595
+ setup(build) {
1596
+ build.onLoad({ filter: /\.(ts|tsx|js|jsx|mjs|cjs)$/ }, (args) => {
1597
+ let source;
1598
+ try {
1599
+ source = fs2.readFileSync(args.path, "utf8");
1600
+ } catch {
1601
+ return void 0;
1602
+ }
1603
+ const importer = args.path;
1604
+ const importerOutsideAppDir = appDirAbs ? !isInsideDir(importer, appDirAbs) : false;
1605
+ let modified = false;
1606
+ const newSource = source.replace(SPEC_RE, (full, prefix, quote, specifier) => {
1607
+ if (specifier.startsWith("/") || specifier.startsWith("file:") || specifier.startsWith("node:")) {
1608
+ return full;
1609
+ }
1610
+ if (specifier.startsWith("./") || specifier.startsWith("../")) {
1611
+ const resolved = resolveRelativeSpecifier(importer, specifier);
1612
+ if (resolved) {
1613
+ if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
1614
+ return full;
1615
+ }
1616
+ if (appDirAbs && importerOutsideAppDir && isInsideDir(resolved, appDirAbs)) {
1617
+ modified = true;
1618
+ return `${prefix}${quote}${toStrippedProdImportPath(
1619
+ resolved,
1620
+ options.rootDir
1621
+ )}${quote}`;
1622
+ }
1623
+ modified = true;
1624
+ return `${prefix}${quote}${toProdImportPath(resolved, importer)}${quote}`;
1625
+ }
1626
+ return full;
1627
+ }
1628
+ const candidates = resolveAlias(specifier, config);
1629
+ for (const candidate of candidates) {
1630
+ for (const ext of SOURCE_EXTS) {
1631
+ const file = candidate + ext;
1632
+ if (fs2.existsSync(file)) {
1633
+ modified = true;
1634
+ if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
1635
+ return `${prefix}${quote}${toStrippedProdImportPath(
1636
+ file,
1637
+ options.rootDir
1638
+ )}${quote}`;
1639
+ }
1640
+ return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
1641
+ }
1642
+ }
1643
+ for (const indexExt of INDEX_EXTS) {
1644
+ const file = candidate + indexExt;
1645
+ if (fs2.existsSync(file)) {
1646
+ modified = true;
1647
+ if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
1648
+ return `${prefix}${quote}${toStrippedProdImportPath(
1649
+ file,
1650
+ options.rootDir
1651
+ )}${quote}`;
1652
+ }
1653
+ return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
1654
+ }
1655
+ }
1656
+ }
1657
+ return full;
1658
+ });
1659
+ if (!modified) return void 0;
1660
+ return { contents: newSource, loader: "default" };
1661
+ });
1602
1662
  }
1603
- );
1663
+ };
1604
1664
  }
1605
- function loadEnv(rootDir) {
1606
- const env = resolveEnv();
1607
- const files = getEnvFiles(env);
1608
- const merged = {};
1609
- for (const file of files) {
1610
- const filePath = path3.join(rootDir, file);
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);
1665
+ function buildAliasPlugins(rootDir) {
1666
+ const tsconfig = readTsconfig(rootDir);
1667
+ return [createAliasPlugin(tsconfig ?? { baseUrl: ".", paths: {} }, { rootDir })];
1668
+ }
1669
+
1670
+ // src/cli/compileDevRoutes.ts
1671
+ var APP_DIR2 = "src";
1672
+ async function compileDevRoutes(options) {
1673
+ const { rootDir, dist, files, logLevel = "silent" } = options;
1674
+ const entryPoints = files ?? await fg([`${APP_DIR2}/**/*.ts`], {
1675
+ cwd: rootDir,
1676
+ onlyFiles: true,
1677
+ absolute: true,
1678
+ ignore: ["**/*.test.ts", "**/*.e2e.test.ts", "**/*.d.ts"]
1679
+ });
1680
+ if (entryPoints.length === 0) {
1681
+ return { compiledFiles: [] };
1615
1682
  }
1616
- for (const [key, value] of Object.entries(merged)) {
1617
- if (process.env[key] === void 0) {
1618
- process.env[key] = value;
1619
- }
1683
+ const absDist = path4.resolve(rootDir, dist);
1684
+ await fs3.promises.mkdir(absDist, { recursive: true });
1685
+ const plugins = buildAliasPlugins(rootDir);
1686
+ const esbuild = await import("esbuild");
1687
+ const outbase = path4.resolve(rootDir, APP_DIR2);
1688
+ const result = await esbuild.build({
1689
+ entryPoints,
1690
+ outdir: absDist,
1691
+ outbase,
1692
+ bundle: false,
1693
+ platform: "node",
1694
+ format: "esm",
1695
+ sourcemap: true,
1696
+ packages: "external",
1697
+ plugins,
1698
+ logLevel,
1699
+ write: false
1700
+ });
1701
+ if (result.outputFiles) {
1702
+ await Promise.all(
1703
+ result.outputFiles.map(async (file) => {
1704
+ await fs3.promises.mkdir(path4.dirname(file.path), { recursive: true });
1705
+ const tmp = `${file.path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
1706
+ await fs3.promises.writeFile(tmp, file.contents);
1707
+ await fs3.promises.rename(tmp, file.path);
1708
+ })
1709
+ );
1620
1710
  }
1711
+ return { compiledFiles: entryPoints };
1621
1712
  }
1622
1713
 
1623
- // src/errors/FaapiError.ts
1624
- var FaapiError = class extends Error {
1625
- constructor(code, message, statusCode) {
1626
- super(message);
1627
- this.code = code;
1628
- this.statusCode = statusCode;
1629
- this.name = "FaapiError";
1714
+ // src/cli/compileOnDemand.ts
1715
+ init_generateSchemaFiles();
1716
+ init_generateSchemaFiles();
1717
+ function isProductFresh(sourceAbsPath, productAbsPath) {
1718
+ try {
1719
+ const srcStat = fs5.statSync(sourceAbsPath);
1720
+ const prodStat = fs5.statSync(productAbsPath);
1721
+ return prodStat.mtimeMs >= srcStat.mtimeMs;
1722
+ } catch {
1723
+ return false;
1630
1724
  }
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
1725
  }
1640
- var ValidationError = class extends FaapiError {
1641
- constructor(message, issues) {
1642
- super("VALIDATION_ERROR", message, deriveStatusCode(issues));
1643
- this.issues = issues;
1644
- this.name = "ValidationError";
1726
+ function createDevOnDemandState() {
1727
+ return {
1728
+ enabled: false,
1729
+ distDir: void 0,
1730
+ compiledFiles: /* @__PURE__ */ new Set(),
1731
+ generatedSchemas: /* @__PURE__ */ new Set(),
1732
+ inFlightCompilations: /* @__PURE__ */ new Map(),
1733
+ inFlightSchemaGenerations: /* @__PURE__ */ new Map()
1734
+ };
1735
+ }
1736
+ var state = createDevOnDemandState();
1737
+ function clearCompiledFiles() {
1738
+ state.compiledFiles.clear();
1739
+ state.inFlightCompilations.clear();
1740
+ }
1741
+ async function ensureCompiled(sourceAbsPath, rootDir, dist) {
1742
+ const inFlight = state.inFlightCompilations.get(sourceAbsPath);
1743
+ if (inFlight) {
1744
+ await inFlight.catch(() => {
1745
+ });
1746
+ return false;
1645
1747
  }
1646
- issues;
1647
- };
1648
- var RouteNotFoundError = class extends FaapiError {
1649
- constructor(path14) {
1650
- super("ROUTE_NOT_FOUND", `Route not found: ${path14}`, 404);
1651
- this.name = "RouteNotFoundError";
1748
+ if (state.compiledFiles.has(sourceAbsPath)) {
1749
+ return false;
1652
1750
  }
1653
- };
1654
- var MethodNotAllowedError = class extends FaapiError {
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";
1751
+ if (!fs5.existsSync(sourceAbsPath)) {
1752
+ return false;
1659
1753
  }
1660
- allowedMethods;
1661
- };
1662
- var InternalError = class extends FaapiError {
1663
- constructor(message) {
1664
- super("INTERNAL_ERROR", message, 500);
1665
- this.name = "InternalError";
1754
+ const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);
1755
+ if (productPath && isProductFresh(sourceAbsPath, productPath)) {
1756
+ state.compiledFiles.add(sourceAbsPath);
1757
+ return false;
1666
1758
  }
1667
- };
1668
- var ModuleLoadError = class extends FaapiError {
1669
- constructor(filePath, reason) {
1670
- super("MODULE_LOAD_ERROR", `Failed to load module ${filePath}: ${reason}`, 500);
1671
- this.name = "ModuleLoadError";
1759
+ const compilePromise = (async () => {
1760
+ await compileDevRoutes({
1761
+ rootDir,
1762
+ dist,
1763
+ files: [sourceAbsPath],
1764
+ logLevel: "silent"
1765
+ });
1766
+ state.compiledFiles.add(sourceAbsPath);
1767
+ })();
1768
+ state.inFlightCompilations.set(sourceAbsPath, compilePromise);
1769
+ try {
1770
+ await compilePromise;
1771
+ return true;
1772
+ } finally {
1773
+ state.inFlightCompilations.delete(sourceAbsPath);
1672
1774
  }
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
- });
1696
1775
  }
1697
-
1698
- // src/router/detectRouteConflicts.ts
1699
- function detectRouteConflicts(routes) {
1700
- const map = /* @__PURE__ */ new Map();
1701
- for (const route of routes) {
1702
- const key = `${route.method} ${route.urlPath}`;
1703
- const existing = map.get(key);
1704
- if (existing) {
1705
- existing.files.push(route.filePath);
1706
- } else {
1707
- map.set(key, {
1708
- method: route.method,
1709
- urlPath: route.urlPath,
1710
- files: [route.filePath]
1711
- });
1712
- }
1776
+ function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
1777
+ const rel = path6.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
1778
+ if (!rel.startsWith("src/")) return null;
1779
+ const relWithoutSrc = rel.slice(4);
1780
+ const jsRel = relWithoutSrc.replace(/\.ts$/, ".js");
1781
+ return path6.resolve(rootDir, dist, jsRel);
1782
+ }
1783
+ function clearGeneratedSchemas() {
1784
+ state.generatedSchemas.clear();
1785
+ state.inFlightSchemaGenerations.clear();
1786
+ }
1787
+ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist) {
1788
+ const inFlight = state.inFlightSchemaGenerations.get(schemaPath);
1789
+ if (inFlight) {
1790
+ await inFlight.catch(() => {
1791
+ });
1792
+ return false;
1713
1793
  }
1714
- const conflicts = [];
1715
- for (const conflict of map.values()) {
1716
- if (conflict.files.length > 1) {
1717
- conflicts.push(conflict);
1718
- }
1794
+ if (state.generatedSchemas.has(schemaPath)) {
1795
+ return false;
1796
+ }
1797
+ const prodAbsPath = path6.resolve(rootDir, routeFilePath);
1798
+ const sourceAbsPath = prodPathToSourcePath(prodAbsPath, rootDir, dist);
1799
+ if (!fs5.existsSync(sourceAbsPath)) {
1800
+ return false;
1801
+ }
1802
+ if (isProductFresh(sourceAbsPath, schemaPath)) {
1803
+ state.generatedSchemas.add(schemaPath);
1804
+ return false;
1805
+ }
1806
+ const fileRoutes = routes.filter((r) => r.filePath === routeFilePath);
1807
+ if (fileRoutes.length === 0) {
1808
+ return false;
1809
+ }
1810
+ const sourceRelPath = path6.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
1811
+ const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
1812
+ const generatePromise = (async () => {
1813
+ await generateSchemaFiles(sourceRoutes, rootDir, dist);
1814
+ state.generatedSchemas.add(schemaPath);
1815
+ })();
1816
+ state.inFlightSchemaGenerations.set(schemaPath, generatePromise);
1817
+ try {
1818
+ await generatePromise;
1819
+ return true;
1820
+ } finally {
1821
+ state.inFlightSchemaGenerations.delete(schemaPath);
1719
1822
  }
1720
- return conflicts;
1721
1823
  }
1722
-
1723
- // src/server/createServer.ts
1724
- import {
1725
- createServer as createHttpServer
1726
- } from "http";
1727
- import { createSecureServer as createHttp2SecureServer } from "http2";
1728
- import { readFileSync } from "fs";
1729
- import { Readable as Readable2 } from "stream";
1730
- import path10 from "path";
1731
-
1732
- // src/router/matchRoute.ts
1733
- function matchRoute(routes, method, path14) {
1824
+ async function deleteSchemaFiles(routes, rootDir, dist) {
1825
+ const deleted = /* @__PURE__ */ new Set();
1734
1826
  for (const route of routes) {
1735
- if (route.method !== method) {
1736
- continue;
1737
- }
1738
- if (!route.isDynamic) {
1739
- if (route.urlPath === path14) {
1740
- return { route, params: {} };
1741
- }
1742
- continue;
1743
- }
1744
- const params = matchDynamicPath(route.urlPath, path14, route.paramNames, route.isCatchAll);
1745
- if (params !== null) {
1746
- return { route, params };
1827
+ const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);
1828
+ if (deleted.has(schemaPath)) continue;
1829
+ deleted.add(schemaPath);
1830
+ try {
1831
+ await fs5.promises.unlink(schemaPath);
1832
+ } catch {
1747
1833
  }
1748
1834
  }
1749
- return null;
1750
1835
  }
1751
- function matchWsRoute(wsRoutes, path14) {
1752
- for (const route of wsRoutes) {
1753
- if (!route.isDynamic) {
1754
- if (route.urlPath === path14) {
1755
- return { route, params: {} };
1836
+ function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
1837
+ const rel = path6.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
1838
+ let relWithoutDist = rel;
1839
+ if (relWithoutDist.startsWith(`${dist}/`)) {
1840
+ relWithoutDist = relWithoutDist.slice(dist.length + 1);
1841
+ }
1842
+ const srcRel = `src/${relWithoutDist}`;
1843
+ const tsRel = srcRel.replace(/\.js$/, ".ts");
1844
+ const tsAbs = path6.resolve(rootDir, tsRel);
1845
+ if (fs5.existsSync(tsAbs)) return tsAbs;
1846
+ return path6.resolve(rootDir, srcRel);
1847
+ }
1848
+ function isDevOnDemandEnabled() {
1849
+ return state.enabled;
1850
+ }
1851
+ function getDevDist() {
1852
+ return state.distDir;
1853
+ }
1854
+
1855
+ // src/loader/loadAgentModule.ts
1856
+ async function loadAgentModule(filePath, hasRun, rootDir) {
1857
+ if (isDevOnDemandEnabled() && rootDir) {
1858
+ const dist = getDevDist();
1859
+ if (dist) {
1860
+ const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
1861
+ if (sourcePath && fs6.existsSync(sourcePath)) {
1862
+ try {
1863
+ await ensureCompiled(sourcePath, rootDir, dist);
1864
+ } catch (compileErr) {
1865
+ const reason = compileErr instanceof Error ? compileErr.message : String(compileErr);
1866
+ throw new Error(`Failed to compile agent module "${sourcePath}": ${reason}`, {
1867
+ cause: compileErr
1868
+ });
1869
+ }
1756
1870
  }
1757
- continue;
1758
1871
  }
1759
- const params = matchDynamicPath(route.urlPath, path14, route.paramNames, route.isCatchAll);
1760
- if (params !== null) {
1761
- return { route, params };
1872
+ }
1873
+ let module;
1874
+ try {
1875
+ module = await importWithCacheBust(filePath, isDevOnDemandEnabled());
1876
+ } catch (err) {
1877
+ const reason = err instanceof Error ? err.message : String(err);
1878
+ throw new Error(`Failed to load agent module "${filePath}": ${reason}`, { cause: err });
1879
+ }
1880
+ let run;
1881
+ if (hasRun) {
1882
+ const runExport = resolveExport(module, "run");
1883
+ if (typeof runExport !== "function") {
1884
+ throw new Error(
1885
+ `Agent module "${filePath}" does not export a valid "run" function (hasRun=true). Expected a function, got ${runExport === void 0 ? "undefined" : typeof runExport}.`
1886
+ );
1762
1887
  }
1888
+ run = runExport;
1763
1889
  }
1764
- return null;
1890
+ return { run };
1765
1891
  }
1766
- function matchDynamicPath(pattern, path14, paramNames, isCatchAll) {
1767
- const patternSegments = pattern.split("/").filter(Boolean);
1768
- const pathSegments = path14.split("/").filter(Boolean);
1769
- if (isCatchAll) {
1770
- const nonCatchAllCount = patternSegments.length - 1;
1771
- if (pathSegments.length <= nonCatchAllCount) {
1772
- return null;
1773
- }
1774
- const params2 = {};
1775
- for (let i = 0; i < nonCatchAllCount; i++) {
1776
- const patternSeg = patternSegments[i];
1777
- const pathSeg = pathSegments[i];
1778
- if (patternSeg.startsWith(":")) {
1779
- const paramName = patternSeg.slice(1);
1780
- params2[paramName] = pathSeg;
1781
- } else if (patternSeg !== pathSeg) {
1782
- return null;
1892
+
1893
+ // src/loader/loadToolModule.ts
1894
+ import fs7 from "fs";
1895
+ async function loadToolModule(filePath, functionName, rootDir) {
1896
+ if (isDevOnDemandEnabled() && rootDir) {
1897
+ const dist = getDevDist();
1898
+ if (dist) {
1899
+ const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
1900
+ if (sourcePath && fs7.existsSync(sourcePath)) {
1901
+ try {
1902
+ await ensureCompiled(sourcePath, rootDir, dist);
1903
+ } catch (compileErr) {
1904
+ const reason = compileErr instanceof Error ? compileErr.message : String(compileErr);
1905
+ throw new Error(`Failed to compile tool module "${sourcePath}": ${reason}`, {
1906
+ cause: compileErr
1907
+ });
1908
+ }
1783
1909
  }
1784
1910
  }
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
1911
  }
1796
- const params = {};
1797
- for (let i = 0; i < patternSegments.length; i++) {
1798
- const patternSeg = patternSegments[i];
1799
- const pathSeg = pathSegments[i];
1800
- if (patternSeg.startsWith(":")) {
1801
- const paramName = patternSeg.slice(1);
1802
- params[paramName] = pathSeg;
1803
- } else if (patternSeg !== pathSeg) {
1804
- return null;
1805
- }
1912
+ let module;
1913
+ try {
1914
+ module = await importWithCacheBust(filePath, isDevOnDemandEnabled());
1915
+ } catch (err) {
1916
+ const reason = err instanceof Error ? err.message : String(err);
1917
+ throw new Error(`Failed to load tool module "${filePath}": ${reason}`, { cause: err });
1806
1918
  }
1807
- if (Object.keys(params).length !== paramNames.length) {
1808
- return null;
1919
+ const handler = resolveExport(module, functionName);
1920
+ if (typeof handler !== "function") {
1921
+ throw new Error(
1922
+ `Tool module "${filePath}" does not export a valid function for "${functionName}". Expected a function, got ${handler === void 0 ? "undefined" : typeof handler}.`
1923
+ );
1809
1924
  }
1810
- return params;
1925
+ return { handler, functionName };
1811
1926
  }
1812
1927
 
1813
- // src/loader/loadRouteModule.ts
1814
- import fs8 from "fs";
1928
+ // src/loader/loadToolSchema.ts
1929
+ import { existsSync as existsSync2 } from "fs";
1815
1930
 
1816
- // src/loader/resolveExports.ts
1817
- function resolveExport(module, exportName) {
1818
- if (exportName in module && typeof module[exportName] !== "undefined") {
1819
- return module[exportName];
1820
- }
1821
- const defaultExport = module.default;
1822
- if (defaultExport !== null && typeof defaultExport === "object") {
1823
- const value = defaultExport[exportName];
1824
- if (value !== void 0) {
1825
- return value;
1931
+ // src/cli/generateToolArtifacts.ts
1932
+ import path7 from "path";
1933
+ import fs8 from "fs/promises";
1934
+ import { existsSync } from "fs";
1935
+
1936
+ // src/ast/extractToolMetadata.ts
1937
+ import ts7 from "typescript";
1938
+ function extractToolMetadata(program, filePath, functionName, pathMeta) {
1939
+ const sourceFile = program.getSourceFile(filePath);
1940
+ if (!sourceFile) return null;
1941
+ const found = findExportedFunction(sourceFile, functionName);
1942
+ if (!found) return null;
1943
+ const { fn, jsDocOwner } = found;
1944
+ const jsDoc = getJSDocFromNode(jsDocOwner);
1945
+ const description = extractDescription(jsDoc);
1946
+ const toolNameOverride = extractToolTagValue(jsDoc);
1947
+ const inputTypeName = getFirstParamTypeName(fn, sourceFile);
1948
+ return {
1949
+ name: toolNameOverride ?? pathMeta.name,
1950
+ description,
1951
+ inputTypeName,
1952
+ filePath: pathMeta.filePath,
1953
+ functionName
1954
+ };
1955
+ }
1956
+ function findExportedFunction(sourceFile, functionName) {
1957
+ let result = null;
1958
+ ts7.forEachChild(sourceFile, (node) => {
1959
+ if (result) return;
1960
+ if (ts7.isFunctionDeclaration(node) && hasExportModifier(node) && node.name?.text === functionName) {
1961
+ result = { fn: node, jsDocOwner: node };
1962
+ return;
1826
1963
  }
1827
- }
1964
+ if (ts7.isVariableStatement(node) && hasExportModifier(node)) {
1965
+ for (const decl of node.declarationList.declarations) {
1966
+ if (result) break;
1967
+ const nameText = ts7.isIdentifier(decl.name) ? decl.name.text : decl.name.getText(sourceFile);
1968
+ if (nameText !== functionName || !decl.initializer) continue;
1969
+ if (ts7.isArrowFunction(decl.initializer) || ts7.isFunctionExpression(decl.initializer)) {
1970
+ result = { fn: decl.initializer, jsDocOwner: node };
1971
+ }
1972
+ }
1973
+ }
1974
+ });
1975
+ return result;
1976
+ }
1977
+ function hasExportModifier(node) {
1978
+ if (!ts7.canHaveModifiers(node)) return false;
1979
+ const modifiers = ts7.getModifiers(node);
1980
+ return !!modifiers?.some((m) => m.kind === ts7.SyntaxKind.ExportKeyword);
1981
+ }
1982
+ function getJSDocFromNode(node) {
1983
+ const apiDocs = ts7.getJSDocCommentsAndTags(node).filter((entry) => ts7.isJSDoc(entry));
1984
+ if (apiDocs.length > 0) return apiDocs[0];
1985
+ const directDocs = node.jsDoc;
1986
+ if (directDocs && directDocs.length > 0) return directDocs[0];
1828
1987
  return void 0;
1829
1988
  }
1830
-
1831
- // src/loader/validateRouteModule.ts
1832
- function validateRouteModule(value, method, filePath) {
1833
- if (typeof value !== "function") {
1834
- throw new Error(
1835
- `Route module "${filePath}" does not export a valid handler for method "${method}". Expected a function, got ${typeof value}.`
1836
- );
1989
+ function extractDescription(jsDoc) {
1990
+ if (!jsDoc) return void 0;
1991
+ if (typeof jsDoc.comment !== "string") return void 0;
1992
+ const trimmed = jsDoc.comment.trim();
1993
+ return trimmed || void 0;
1994
+ }
1995
+ function extractToolTagValue(jsDoc) {
1996
+ if (!jsDoc || !jsDoc.tags) return void 0;
1997
+ for (const tag of jsDoc.tags) {
1998
+ if (tag.tagName.text !== "tool") continue;
1999
+ if (typeof tag.comment !== "string") return void 0;
2000
+ const text = tag.comment.trim();
2001
+ if (!text) return void 0;
2002
+ const cleaned = text.replace(/^\{|\}$/g, "").trim();
2003
+ return cleaned || void 0;
1837
2004
  }
2005
+ return void 0;
2006
+ }
2007
+ function getFirstParamTypeName(fn, sourceFile) {
2008
+ const firstParam = fn.parameters[0];
2009
+ if (!firstParam) return void 0;
2010
+ if (!firstParam.type) return void 0;
2011
+ if (!ts7.isTypeReferenceNode(firstParam.type)) return void 0;
2012
+ return firstParam.type.typeName.getText(sourceFile);
1838
2013
  }
1839
2014
 
1840
- // src/cli/compileOnDemand.ts
1841
- import path8 from "path";
1842
- import fs7 from "fs";
2015
+ // src/cli/generateToolArtifacts.ts
2016
+ init_createProgram();
2017
+ init_extractHandlerTypes();
2018
+ init_generateZodSchema();
2019
+ init_generateSchemaFiles();
2020
+ var TOOLS_FILE = "faapi-tools.js";
2021
+ function getToolSchemaOutputPath(sourceFile, dist, rootDir) {
2022
+ let rel = sourceFile.replace(/\\/g, "/");
2023
+ if (rel.startsWith("src/")) {
2024
+ rel = rel.slice(4);
2025
+ }
2026
+ const idx = rel.lastIndexOf("/");
2027
+ const relDir = idx >= 0 ? rel.slice(0, idx) : "";
2028
+ return path7.resolve(rootDir, dist, relDir, "zod.js");
2029
+ }
2030
+ function getRuntimeToolSchemaPath(filePath, dist, rootDir) {
2031
+ let rel = filePath.replace(/\\/g, "/");
2032
+ if (rel.startsWith("src/")) {
2033
+ rel = rel.slice(4);
2034
+ } else if (rel.startsWith(`${dist}/`)) {
2035
+ rel = rel.slice(dist.length + 1);
2036
+ }
2037
+ const idx = rel.lastIndexOf("/");
2038
+ const relDir = idx >= 0 ? rel.slice(0, idx) : "";
2039
+ return path7.resolve(rootDir, dist, relDir, "zod.js");
2040
+ }
2041
+ function toProdFilePath(filePath, dist) {
2042
+ let rel = filePath.replace(/\\/g, "/");
2043
+ if (rel.startsWith("src/")) {
2044
+ rel = rel.slice(4);
2045
+ }
2046
+ const jsPath = rel.replace(/\.ts$/, ".js");
2047
+ return jsPath.startsWith(`${dist}/`) ? jsPath : `${dist}/${jsPath}`;
2048
+ }
2049
+ function serializeTools(tools, dist = "dist") {
2050
+ return tools.map((t) => ({
2051
+ name: t.name,
2052
+ functionName: t.functionName,
2053
+ description: t.description,
2054
+ inputTypeName: t.inputTypeName,
2055
+ filePath: toProdFilePath(t.filePath, dist)
2056
+ }));
2057
+ }
2058
+ async function writeToolsModule(manifest, outputPath) {
2059
+ const dir = path7.dirname(outputPath);
2060
+ await fs8.mkdir(dir, { recursive: true });
2061
+ const content = `// \u81EA\u52A8\u751F\u6210,\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91(faapi build/dev \u4EA7\u7269)
2062
+ export const tools = ${JSON.stringify(manifest, null, 2)};
2063
+ `;
2064
+ await fs8.writeFile(outputPath, content, "utf-8");
2065
+ }
2066
+ function hydrateTools(manifest) {
2067
+ return manifest.map((t) => ({
2068
+ name: t.name,
2069
+ functionName: t.functionName,
2070
+ description: t.description ?? void 0,
2071
+ inputTypeName: t.inputTypeName ?? void 0,
2072
+ filePath: t.filePath
2073
+ }));
2074
+ }
2075
+ function collectToolSchemaSources(tools, rootDir) {
2076
+ const toolsByFile = /* @__PURE__ */ new Map();
2077
+ for (const tool of tools) {
2078
+ if (!tool.inputTypeName) continue;
2079
+ const absPath = path7.resolve(rootDir, tool.filePath);
2080
+ let list = toolsByFile.get(absPath);
2081
+ if (!list) {
2082
+ list = [];
2083
+ toolsByFile.set(absPath, list);
2084
+ }
2085
+ list.push(tool);
2086
+ }
2087
+ const allTypesByFile = /* @__PURE__ */ new Map();
2088
+ for (const filePath of toolsByFile.keys()) {
2089
+ const program = createProgram(filePath);
2090
+ const allTypes = extractAllTypes(program, filePath);
2091
+ allTypesByFile.set(filePath, allTypes);
2092
+ }
2093
+ const sources = [];
2094
+ for (const [filePath, fileTools] of toolsByFile) {
2095
+ const program = createProgram(filePath);
2096
+ for (const tool of fileTools) {
2097
+ const inputTypeName = tool.inputTypeName;
2098
+ const typeInfo = extractTypeInfo(program, filePath, inputTypeName);
2099
+ sources.push({
2100
+ name: tool.name,
2101
+ filePath,
2102
+ schemaName: inputTypeName,
2103
+ // schema 名 = inputTypeName
2104
+ typeInfo
2105
+ });
2106
+ }
2107
+ }
2108
+ return { sources, allTypesByFile };
2109
+ }
2110
+ function generateToolSchemaFileSource(sources, allTypes, helpersImportPath) {
2111
+ const resolveType = (name) => allTypes.get(name)?.runtimeType;
2112
+ const lines = ["import { z } from 'zod';"];
2113
+ const schemaBlocks = [];
2114
+ for (const source of sources) {
2115
+ const { schemaName, typeInfo } = source;
2116
+ if (!typeInfo) {
2117
+ continue;
2118
+ }
2119
+ const coerce = false;
2120
+ const block = [`// ${source.name} \u2192 ${schemaName}`];
2121
+ const schemaCode = generateZodSchemaSource(typeInfo, resolveType, schemaName, coerce).replace(
2122
+ /^import \{ z \} from 'zod';\s*\n\s*\n/,
2123
+ ""
2124
+ );
2125
+ block.push(schemaCode);
2126
+ block.push("");
2127
+ schemaBlocks.push(block.join("\n"));
2128
+ }
2129
+ const allSchemaCode = schemaBlocks.join("\n");
2130
+ if (helpersImportPath && usesCoerceHelpers(allSchemaCode)) {
2131
+ lines.push(
2132
+ `import { coerceNumber, coerceBoolean, coerceMap, coerceSet } from '${helpersImportPath}';`
2133
+ );
2134
+ }
2135
+ lines.push("");
2136
+ lines.push(...schemaBlocks);
2137
+ return lines.join("\n").replace(/\n+$/, "\n");
2138
+ }
2139
+ async function maybeGenerateHelpers(allSourceCode, distDir) {
2140
+ if (!usesCoerceHelpers(allSourceCode)) return;
2141
+ const helpersPath = path7.resolve(distDir, HELPERS_FILENAME);
2142
+ if (existsSync(helpersPath)) return;
2143
+ await fs8.mkdir(path7.dirname(helpersPath), { recursive: true });
2144
+ await fs8.writeFile(helpersPath, generateHelpersFileSource(), "utf-8");
2145
+ }
2146
+ async function generateToolArtifacts(tools, rootDir, dist, options) {
2147
+ const metadata = [];
2148
+ for (const manifest of tools) {
2149
+ const absPath = path7.resolve(rootDir, manifest.filePath);
2150
+ const program = createProgram(absPath);
2151
+ const result = extractToolMetadata(program, absPath, manifest.functionName, {
2152
+ name: manifest.name,
2153
+ filePath: manifest.filePath
2154
+ });
2155
+ if (result) {
2156
+ metadata.push(result);
2157
+ }
2158
+ }
2159
+ const serialized = serializeTools(metadata, dist);
2160
+ const toolsPath = path7.resolve(rootDir, dist, TOOLS_FILE);
2161
+ await writeToolsModule(serialized, toolsPath);
2162
+ if (options?.skipSchema) {
2163
+ return metadata;
2164
+ }
2165
+ if (metadata.length === 0) {
2166
+ return metadata;
2167
+ }
2168
+ const { sources, allTypesByFile } = collectToolSchemaSources(metadata, rootDir);
2169
+ if (sources.length === 0) {
2170
+ return metadata;
2171
+ }
2172
+ const sourcesByFile = /* @__PURE__ */ new Map();
2173
+ for (const source of sources) {
2174
+ let list = sourcesByFile.get(source.filePath);
2175
+ if (!list) {
2176
+ list = [];
2177
+ sourcesByFile.set(source.filePath, list);
2178
+ }
2179
+ list.push(source);
2180
+ }
2181
+ const fileEntries = [];
2182
+ for (const [filePath, fileSources] of sourcesByFile) {
2183
+ const relFile = path7.relative(rootDir, filePath).replace(/\\/g, "/");
2184
+ const outputPath = getToolSchemaOutputPath(relFile, dist, rootDir);
2185
+ const allTypes = allTypesByFile.get(filePath) ?? /* @__PURE__ */ new Map();
2186
+ let relForDir = relFile;
2187
+ if (relForDir.startsWith("src/")) {
2188
+ relForDir = relForDir.slice(4);
2189
+ }
2190
+ const dirIdx = relForDir.lastIndexOf("/");
2191
+ const zodRelDir = dirIdx >= 0 ? relForDir.slice(0, dirIdx) : "";
2192
+ const helpersImportPath = getHelpersImportPath(zodRelDir);
2193
+ const source = generateToolSchemaFileSource(fileSources, allTypes, helpersImportPath);
2194
+ fileEntries.push({ outputPath, source });
2195
+ }
2196
+ const allSourceCode = fileEntries.map((e) => e.source).join("\n");
2197
+ const distDir = path7.resolve(rootDir, dist);
2198
+ await maybeGenerateHelpers(allSourceCode, distDir);
2199
+ await Promise.all(
2200
+ fileEntries.map(({ outputPath, source }) => writeToolSchemaFile(outputPath, source))
2201
+ );
2202
+ return metadata;
2203
+ }
2204
+ async function writeToolSchemaFile(outputPath, source) {
2205
+ await fs8.mkdir(path7.dirname(outputPath), { recursive: true });
2206
+ await fs8.writeFile(outputPath, source, "utf-8");
2207
+ }
1843
2208
 
1844
- // src/cli/compileDevRoutes.ts
1845
- import path6 from "path";
1846
- import fs5 from "fs";
1847
- import fg from "fast-glob";
2209
+ // src/loader/loadToolSchema.ts
2210
+ function getDist() {
2211
+ if (isDevOnDemandEnabled()) {
2212
+ return getDevDist() ?? ".faapi";
2213
+ }
2214
+ return process.env.FAAPI_DIST ?? "dist";
2215
+ }
2216
+ async function loadToolSchema(tool, rootDir) {
2217
+ if (!tool.inputTypeName) return void 0;
2218
+ const schemaName = `${tool.inputTypeName}Schema`;
2219
+ const dist = getDist();
2220
+ const zodPath = getRuntimeToolSchemaPath(tool.filePath, dist, rootDir ?? process.cwd());
2221
+ if (!existsSync2(zodPath)) return void 0;
2222
+ try {
2223
+ const mod = await importWithCacheBust(zodPath, isDevOnDemandEnabled());
2224
+ const schema = mod[`${tool.inputTypeName}Schema`];
2225
+ if (!schema) return void 0;
2226
+ return { schema, schemaName };
2227
+ } catch {
2228
+ return void 0;
2229
+ }
2230
+ }
1848
2231
 
1849
- // src/cli/aliasPlugin.ts
1850
- import path5 from "path";
1851
- import fs4 from "fs";
2232
+ // src/injection/agentHandle.ts
2233
+ var currentFactory = null;
2234
+ function registerAgentHandleFactory(factory) {
2235
+ currentFactory = factory;
2236
+ }
2237
+ function getAgentHandle(ctx) {
2238
+ if (currentFactory === null) return void 0;
2239
+ return currentFactory(ctx);
2240
+ }
2241
+ function clearAgentHandleFactory() {
2242
+ currentFactory = null;
2243
+ }
1852
2244
 
1853
- // src/utils/resolveAlias.ts
1854
- function resolveAlias(specifier, config) {
1855
- const candidates = [];
1856
- for (const [pattern, targets] of Object.entries(config.paths)) {
1857
- const wildcardIndex = pattern.indexOf("*");
1858
- if (wildcardIndex === -1) {
1859
- if (specifier === pattern) {
1860
- candidates.push(...targets);
2245
+ // src/middleware/cors.ts
2246
+ var DEFAULT_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
2247
+ function cors(options = {}) {
2248
+ const {
2249
+ origin = true,
2250
+ methods = DEFAULT_METHODS,
2251
+ allowedHeaders,
2252
+ exposeHeaders,
2253
+ credentials = false,
2254
+ maxAge
2255
+ } = options;
2256
+ return async (ctx, next) => {
2257
+ const reqOrigin = ctx.headers.get("origin");
2258
+ if (!reqOrigin) {
2259
+ await next();
2260
+ return;
2261
+ }
2262
+ let allowOrigin = null;
2263
+ if (origin === true) {
2264
+ allowOrigin = reqOrigin;
2265
+ } else if (typeof origin === "string") {
2266
+ allowOrigin = reqOrigin === origin ? origin : null;
2267
+ } else if (Array.isArray(origin)) {
2268
+ allowOrigin = origin.includes(reqOrigin) ? reqOrigin : null;
2269
+ }
2270
+ if (!allowOrigin) {
2271
+ await next();
2272
+ return;
2273
+ }
2274
+ ctx.setHeader("Access-Control-Allow-Origin", allowOrigin);
2275
+ if (origin === true || Array.isArray(origin)) {
2276
+ const existingVary = ctx.headers.get("vary");
2277
+ if (existingVary) {
2278
+ if (!existingVary.toLowerCase().includes("origin")) {
2279
+ ctx.setHeader("Vary", `${existingVary}, Origin`);
2280
+ }
2281
+ } else {
2282
+ ctx.setHeader("Vary", "Origin");
1861
2283
  }
1862
- continue;
1863
2284
  }
1864
- const prefix = pattern.slice(0, wildcardIndex);
1865
- const suffix = pattern.slice(wildcardIndex + 1);
1866
- if (specifier.startsWith(prefix) && specifier.endsWith(suffix) && specifier.length >= prefix.length + suffix.length) {
1867
- const captured = specifier.slice(prefix.length, specifier.length - suffix.length);
1868
- for (const target of targets) {
1869
- candidates.push(target.replace("*", captured));
1870
- }
2285
+ ctx.setHeader("Access-Control-Allow-Methods", methods.join(", "));
2286
+ if (allowedHeaders) {
2287
+ ctx.setHeader("Access-Control-Allow-Headers", allowedHeaders.join(", "));
2288
+ } else {
2289
+ const requestHeaders = ctx.headers.get("access-control-request-headers");
2290
+ if (requestHeaders) {
2291
+ ctx.setHeader("Access-Control-Allow-Headers", requestHeaders);
2292
+ }
2293
+ }
2294
+ if (exposeHeaders && exposeHeaders.length > 0) {
2295
+ ctx.setHeader("Access-Control-Expose-Headers", exposeHeaders.join(", "));
2296
+ }
2297
+ if (credentials) {
2298
+ ctx.setHeader("Access-Control-Allow-Credentials", "true");
2299
+ }
2300
+ if (maxAge !== void 0) {
2301
+ ctx.setHeader("Access-Control-Max-Age", String(maxAge));
2302
+ }
2303
+ if (ctx.method === "OPTIONS") {
2304
+ return new Response(null, { status: 204 });
2305
+ }
2306
+ await next();
2307
+ };
2308
+ }
2309
+
2310
+ // src/middleware/logger.ts
2311
+ function logger(options = {}) {
2312
+ return async (ctx, next) => {
2313
+ const log = options.log ?? console.log;
2314
+ const start = Date.now();
2315
+ try {
2316
+ const response = await next();
2317
+ const duration = Date.now() - start;
2318
+ const entry = {
2319
+ method: ctx.method,
2320
+ path: ctx.path,
2321
+ status: response.status,
2322
+ durationMs: duration
2323
+ };
2324
+ log(entry, `${ctx.method} ${ctx.path} ${response.status} ${duration}ms`);
2325
+ return response;
2326
+ } catch (err) {
2327
+ const duration = Date.now() - start;
2328
+ const message = err instanceof Error ? err.message : String(err);
2329
+ const status = err?.statusCode ?? 500;
2330
+ const entry = {
2331
+ method: ctx.method,
2332
+ path: ctx.path,
2333
+ status,
2334
+ durationMs: duration,
2335
+ error: message
2336
+ };
2337
+ log(entry, `${ctx.method} ${ctx.path} ${status} ${duration}ms - ${message}`);
2338
+ throw err;
2339
+ }
2340
+ };
2341
+ }
2342
+
2343
+ // src/middleware/helmet.ts
2344
+ var DEFAULTS = {
2345
+ contentSecurityPolicy: "default-src 'self'",
2346
+ xFrameOptions: "SAMEORIGIN",
2347
+ xContentTypeOptions: true,
2348
+ referrerPolicy: "no-referrer",
2349
+ strictTransportSecurity: "max-age=31536000; includeSubDomains",
2350
+ xDnsPrefetchControl: true,
2351
+ xDownloadOptions: true,
2352
+ xPermittedCrossDomainPolicies: "none",
2353
+ crossOriginOpenerPolicy: "same-origin",
2354
+ crossOriginResourcePolicy: "same-origin",
2355
+ crossOriginEmbedderPolicy: false,
2356
+ originAgentCluster: true,
2357
+ xPoweredBy: true
2358
+ };
2359
+ function helmet(options = {}) {
2360
+ const opts = { ...DEFAULTS, ...options };
2361
+ return async (ctx, next) => {
2362
+ if (opts.contentSecurityPolicy !== false) {
2363
+ ctx.setHeader("Content-Security-Policy", opts.contentSecurityPolicy);
2364
+ }
2365
+ if (opts.xFrameOptions !== false) {
2366
+ ctx.setHeader("X-Frame-Options", opts.xFrameOptions);
2367
+ }
2368
+ if (opts.xContentTypeOptions) {
2369
+ ctx.setHeader("X-Content-Type-Options", "nosniff");
2370
+ }
2371
+ if (opts.referrerPolicy !== false) {
2372
+ ctx.setHeader("Referrer-Policy", opts.referrerPolicy);
2373
+ }
2374
+ if (opts.strictTransportSecurity !== false) {
2375
+ ctx.setHeader("Strict-Transport-Security", opts.strictTransportSecurity);
2376
+ }
2377
+ if (opts.xDnsPrefetchControl) {
2378
+ ctx.setHeader("X-DNS-Prefetch-Control", "off");
2379
+ }
2380
+ if (opts.xDownloadOptions) {
2381
+ ctx.setHeader("X-Download-Options", "noopen");
2382
+ }
2383
+ if (opts.xPermittedCrossDomainPolicies !== false) {
2384
+ ctx.setHeader("X-Permitted-Cross-Domain-Policies", opts.xPermittedCrossDomainPolicies);
2385
+ }
2386
+ if (opts.crossOriginOpenerPolicy !== false) {
2387
+ ctx.setHeader("Cross-Origin-Opener-Policy", opts.crossOriginOpenerPolicy);
2388
+ }
2389
+ if (opts.crossOriginResourcePolicy !== false) {
2390
+ ctx.setHeader("Cross-Origin-Resource-Policy", opts.crossOriginResourcePolicy);
2391
+ }
2392
+ if (opts.crossOriginEmbedderPolicy !== false) {
2393
+ ctx.setHeader("Cross-Origin-Embedder-Policy", opts.crossOriginEmbedderPolicy);
1871
2394
  }
1872
- }
1873
- return candidates;
2395
+ if (opts.originAgentCluster) {
2396
+ ctx.setHeader("Origin-Agent-Cluster", "?1");
2397
+ }
2398
+ if (opts.xPoweredBy) {
2399
+ ctx.setHeader("X-Powered-By", "faapi");
2400
+ }
2401
+ return await next();
2402
+ };
1874
2403
  }
1875
2404
 
1876
- // src/utils/readTsconfig.ts
1877
- import ts6 from "typescript";
1878
- import path4 from "path";
1879
- import fs3 from "fs";
1880
- function readTsconfig(rootDir) {
1881
- const tsconfigPath = path4.resolve(rootDir, "tsconfig.json");
1882
- if (!fs3.existsSync(tsconfigPath)) return null;
1883
- const configFile = ts6.readConfigFile(tsconfigPath, ts6.sys.readFile);
1884
- if (configFile.error || !configFile.config) return null;
1885
- const parsed = ts6.parseJsonConfigFileContent(configFile.config, ts6.sys, rootDir);
1886
- const baseUrl = parsed.options.baseUrl ?? rootDir;
1887
- const rawPaths = parsed.options.paths;
1888
- if (!rawPaths) return null;
1889
- const paths = {};
1890
- for (const [pattern, targets] of Object.entries(rawPaths)) {
1891
- paths[pattern] = targets.map((t) => path4.resolve(baseUrl, t));
2405
+ // src/config/loadConfig.ts
2406
+ import path8 from "path";
2407
+ import fs9 from "fs";
2408
+ var CONFIG_PRODUCT_FILE = "faapi-config.js";
2409
+ async function loadConfig(rootDir, dist) {
2410
+ const configProductPath = path8.resolve(rootDir, dist, CONFIG_PRODUCT_FILE);
2411
+ if (fs9.existsSync(configProductPath)) {
2412
+ const module = await importWithCacheBust(configProductPath);
2413
+ return module.default ?? {};
1892
2414
  }
1893
- return { baseUrl, paths };
2415
+ const hasSourceConfig = fs9.existsSync(path8.join(rootDir, "faapi.config.ts")) || fs9.existsSync(path8.join(rootDir, "faapi.config.js"));
2416
+ if (hasSourceConfig) {
2417
+ throw new Error(
2418
+ `[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`
2419
+ );
2420
+ }
2421
+ return null;
1894
2422
  }
1895
2423
 
1896
- // src/cli/aliasPlugin.ts
1897
- function toProdExtension(filePath) {
1898
- if (filePath.endsWith(".ts")) return filePath.slice(0, -3) + ".js";
1899
- if (filePath.endsWith(".tsx")) return filePath.slice(0, -4) + ".js";
1900
- if (filePath.endsWith(".jsx")) return filePath.slice(0, -4) + ".js";
1901
- return filePath;
2424
+ // src/cli/loadEnv.ts
2425
+ import fs10 from "fs";
2426
+ import path9 from "path";
2427
+ function resolveEnv() {
2428
+ return process.env.NODE_ENV || "development";
1902
2429
  }
1903
- function toProdImportPath(sourceFile, importer) {
1904
- const importerDir = path5.dirname(importer);
1905
- let rel = path5.relative(importerDir, sourceFile);
1906
- rel = rel.split(path5.sep).join("/");
1907
- if (!rel.startsWith(".")) rel = "./" + rel;
1908
- return toProdExtension(rel);
2430
+ function getEnvFiles(env) {
2431
+ return [".env", ".env.local", `.env.${env}`, `.env.${env}.local`];
1909
2432
  }
1910
- function toRealPath(p) {
1911
- try {
1912
- return fs4.realpathSync(p);
1913
- } catch {
1914
- return p;
2433
+ function parseEnvFile(content, fileVars) {
2434
+ const result = {};
2435
+ const lines = content.replace(/\r\n/g, "\n").split("\n");
2436
+ for (const line of lines) {
2437
+ const trimmed = line.trim();
2438
+ if (!trimmed || trimmed.startsWith("#")) continue;
2439
+ const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(trimmed);
2440
+ if (!match) continue;
2441
+ const [, key, rawValue] = match;
2442
+ const value = parseValue(rawValue, { ...fileVars, ...result });
2443
+ result[key] = value;
1915
2444
  }
2445
+ return result;
1916
2446
  }
1917
- function isInsideDir(filePath, dir) {
1918
- const rel = path5.relative(dir, filePath);
1919
- return rel !== "" && !rel.startsWith("..") && !path5.isAbsolute(rel);
1920
- }
1921
- var APP_DIR = "src";
1922
- function toStrippedProdImportPath(sourceFile, rootDir) {
1923
- const appDirAbs = toRealPath(path5.resolve(rootDir, APP_DIR));
1924
- const sourceReal = toRealPath(sourceFile);
1925
- let rel = path5.relative(appDirAbs, sourceReal);
1926
- rel = rel.split(path5.sep).join("/");
1927
- if (!rel.startsWith(".")) rel = "./" + rel;
1928
- return toProdExtension(rel);
1929
- }
1930
- var PROD_EXTS = [".js", ".mjs", ".cjs"];
1931
- var SOURCE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
1932
- var INDEX_EXTS = [
1933
- "/index.ts",
1934
- "/index.tsx",
1935
- "/index.js",
1936
- "/index.jsx",
1937
- "/index.mjs",
1938
- "/index.cjs"
1939
- ];
1940
- function resolveRelativeSpecifier(importer, specifier) {
1941
- const importerDir = path5.dirname(importer);
1942
- const base = path5.resolve(importerDir, specifier);
1943
- if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
1944
- return fs4.existsSync(base) ? base : null;
1945
- }
1946
- if (/\.(ts|tsx|jsx)$/.test(specifier)) {
1947
- return fs4.existsSync(base) ? base : null;
1948
- }
1949
- for (const ext of SOURCE_EXTS) {
1950
- const file = base + ext;
1951
- if (fs4.existsSync(file)) return file;
2447
+ function parseValue(raw, env) {
2448
+ if (raw === "") return "";
2449
+ if (raw[0] === "'") {
2450
+ const end = raw.indexOf("'", 1);
2451
+ return end === -1 ? raw.slice(1) : raw.slice(1, end);
1952
2452
  }
1953
- for (const indexExt of INDEX_EXTS) {
1954
- const file = base + indexExt;
1955
- if (fs4.existsSync(file)) return file;
2453
+ if (raw[0] === '"') {
2454
+ const match = /^"((?:\\.|[^"\\])*)"/.exec(raw);
2455
+ const inner = match ? match[1] : raw.slice(1);
2456
+ return expandEscapesAndVars(inner, env);
1956
2457
  }
1957
- return null;
2458
+ const commentMatch = /^(.*?)(\s+#.*)$/.exec(raw);
2459
+ const value = commentMatch ? commentMatch[1] : raw;
2460
+ return value.trim();
1958
2461
  }
1959
- function createAliasPlugin(config, options) {
1960
- const SPEC_RE = /(\bfrom\s*|import\s*\(\s*)(['"])([^'"]+)\2/g;
1961
- const appDirAbs = options?.rootDir ? toRealPath(path5.resolve(options.rootDir, APP_DIR)) : null;
1962
- return {
1963
- name: "faapi-alias",
1964
- setup(build) {
1965
- build.onLoad({ filter: /\.(ts|tsx|js|jsx|mjs|cjs)$/ }, (args) => {
1966
- let source;
1967
- try {
1968
- source = fs4.readFileSync(args.path, "utf8");
1969
- } catch {
1970
- return void 0;
1971
- }
1972
- const importer = args.path;
1973
- const importerOutsideAppDir = appDirAbs ? !isInsideDir(importer, appDirAbs) : false;
1974
- let modified = false;
1975
- const newSource = source.replace(SPEC_RE, (full, prefix, quote, specifier) => {
1976
- if (specifier.startsWith("/") || specifier.startsWith("file:") || specifier.startsWith("node:")) {
1977
- return full;
1978
- }
1979
- if (specifier.startsWith("./") || specifier.startsWith("../")) {
1980
- const resolved = resolveRelativeSpecifier(importer, specifier);
1981
- if (resolved) {
1982
- if (PROD_EXTS.some((ext) => specifier.endsWith(ext))) {
1983
- return full;
1984
- }
1985
- if (appDirAbs && importerOutsideAppDir && isInsideDir(resolved, appDirAbs)) {
1986
- modified = true;
1987
- return `${prefix}${quote}${toStrippedProdImportPath(
1988
- resolved,
1989
- options.rootDir
1990
- )}${quote}`;
1991
- }
1992
- modified = true;
1993
- return `${prefix}${quote}${toProdImportPath(resolved, importer)}${quote}`;
1994
- }
1995
- return full;
1996
- }
1997
- const candidates = resolveAlias(specifier, config);
1998
- for (const candidate of candidates) {
1999
- for (const ext of SOURCE_EXTS) {
2000
- const file = candidate + ext;
2001
- if (fs4.existsSync(file)) {
2002
- modified = true;
2003
- if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
2004
- return `${prefix}${quote}${toStrippedProdImportPath(
2005
- file,
2006
- options.rootDir
2007
- )}${quote}`;
2008
- }
2009
- return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
2010
- }
2011
- }
2012
- for (const indexExt of INDEX_EXTS) {
2013
- const file = candidate + indexExt;
2014
- if (fs4.existsSync(file)) {
2015
- modified = true;
2016
- if (appDirAbs && importerOutsideAppDir && isInsideDir(file, appDirAbs)) {
2017
- return `${prefix}${quote}${toStrippedProdImportPath(
2018
- file,
2019
- options.rootDir
2020
- )}${quote}`;
2021
- }
2022
- return `${prefix}${quote}${toProdImportPath(file, importer)}${quote}`;
2023
- }
2024
- }
2025
- }
2026
- return full;
2027
- });
2028
- if (!modified) return void 0;
2029
- return { contents: newSource, loader: "default" };
2030
- });
2462
+ function expandEscapesAndVars(str, env) {
2463
+ const escaped = str.replace(/\\(.)/g, (_, ch) => {
2464
+ switch (ch) {
2465
+ case "n":
2466
+ return "\n";
2467
+ case "r":
2468
+ return "\r";
2469
+ case "t":
2470
+ return " ";
2471
+ case "\\":
2472
+ return "\\";
2473
+ case '"':
2474
+ return '"';
2475
+ default:
2476
+ return ch;
2031
2477
  }
2032
- };
2033
- }
2034
- function buildAliasPlugins(rootDir) {
2035
- const tsconfig = readTsconfig(rootDir);
2036
- return [createAliasPlugin(tsconfig ?? { baseUrl: ".", paths: {} }, { rootDir })];
2037
- }
2038
-
2039
- // src/cli/compileDevRoutes.ts
2040
- var APP_DIR2 = "src";
2041
- async function compileDevRoutes(options) {
2042
- const { rootDir, dist, files, logLevel = "silent" } = options;
2043
- const entryPoints = files ?? await fg([`${APP_DIR2}/**/*.ts`], {
2044
- cwd: rootDir,
2045
- onlyFiles: true,
2046
- absolute: true,
2047
- ignore: ["**/*.test.ts", "**/*.e2e.test.ts", "**/*.d.ts"]
2048
- });
2049
- if (entryPoints.length === 0) {
2050
- return { compiledFiles: [] };
2051
- }
2052
- const absDist = path6.resolve(rootDir, dist);
2053
- await fs5.promises.mkdir(absDist, { recursive: true });
2054
- const plugins = buildAliasPlugins(rootDir);
2055
- const esbuild = await import("esbuild");
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
2478
  });
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
- })
2078
- );
2479
+ return escaped.replace(
2480
+ /\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g,
2481
+ (_, braced, plain) => {
2482
+ const varName = braced || plain;
2483
+ return env[varName] ?? process.env[varName] ?? "";
2484
+ }
2485
+ );
2486
+ }
2487
+ function loadEnv(rootDir) {
2488
+ const env = resolveEnv();
2489
+ const files = getEnvFiles(env);
2490
+ const merged = {};
2491
+ for (const file of files) {
2492
+ const filePath = path9.join(rootDir, file);
2493
+ if (!fs10.existsSync(filePath)) continue;
2494
+ const content = fs10.readFileSync(filePath, "utf-8");
2495
+ const parsed = parseEnvFile(content, merged);
2496
+ Object.assign(merged, parsed);
2497
+ }
2498
+ for (const [key, value] of Object.entries(merged)) {
2499
+ if (process.env[key] === void 0) {
2500
+ process.env[key] = value;
2501
+ }
2079
2502
  }
2080
- return { compiledFiles: entryPoints };
2081
2503
  }
2082
2504
 
2083
- // src/cli/compileOnDemand.ts
2084
- init_generateSchemaFiles();
2085
- init_generateSchemaFiles();
2086
- function isProductFresh(sourceAbsPath, productAbsPath) {
2087
- try {
2088
- const srcStat = fs7.statSync(sourceAbsPath);
2089
- const prodStat = fs7.statSync(productAbsPath);
2090
- return prodStat.mtimeMs >= srcStat.mtimeMs;
2091
- } catch {
2092
- return false;
2505
+ // src/errors/FaapiError.ts
2506
+ var FaapiError = class extends Error {
2507
+ constructor(code, message, statusCode) {
2508
+ super(message);
2509
+ this.code = code;
2510
+ this.statusCode = statusCode;
2511
+ this.name = "FaapiError";
2093
2512
  }
2513
+ code;
2514
+ statusCode;
2515
+ };
2516
+
2517
+ // src/errors/httpErrors.ts
2518
+ function deriveStatusCode(issues) {
2519
+ const has400 = issues.some((i) => i.code === "INVALID_FORMAT" || i.code === "MISSING_FIELD");
2520
+ return has400 ? 400 : 422;
2094
2521
  }
2095
- var compiledFiles = /* @__PURE__ */ new Set();
2096
- function clearCompiledFiles() {
2097
- compiledFiles.clear();
2098
- }
2099
- async function ensureCompiled(sourceAbsPath, rootDir, dist) {
2100
- if (compiledFiles.has(sourceAbsPath)) {
2101
- return false;
2522
+ var ValidationError = class extends FaapiError {
2523
+ constructor(message, issues) {
2524
+ super("VALIDATION_ERROR", message, deriveStatusCode(issues));
2525
+ this.issues = issues;
2526
+ this.name = "ValidationError";
2102
2527
  }
2103
- if (!fs7.existsSync(sourceAbsPath)) {
2104
- return false;
2528
+ issues;
2529
+ };
2530
+ var RouteNotFoundError = class extends FaapiError {
2531
+ constructor(path18) {
2532
+ super("ROUTE_NOT_FOUND", `Route not found: ${path18}`, 404);
2533
+ this.name = "RouteNotFoundError";
2105
2534
  }
2106
- const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);
2107
- if (productPath && isProductFresh(sourceAbsPath, productPath)) {
2108
- compiledFiles.add(sourceAbsPath);
2109
- return false;
2535
+ };
2536
+ var MethodNotAllowedError = class extends FaapiError {
2537
+ constructor(method, path18, allowedMethods) {
2538
+ super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path18}`, 405);
2539
+ this.allowedMethods = allowedMethods;
2540
+ this.name = "MethodNotAllowedError";
2110
2541
  }
2111
- await compileDevRoutes({
2112
- rootDir,
2113
- dist,
2114
- files: [sourceAbsPath],
2115
- logLevel: "silent"
2116
- });
2117
- compiledFiles.add(sourceAbsPath);
2118
- return true;
2119
- }
2120
- function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
2121
- const rel = path8.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
2122
- if (!rel.startsWith("src/")) return null;
2123
- const relWithoutSrc = rel.slice(4);
2124
- const jsRel = relWithoutSrc.replace(/\.ts$/, ".js");
2125
- return path8.resolve(rootDir, dist, jsRel);
2126
- }
2127
- var generatedSchemas = /* @__PURE__ */ new Set();
2128
- function clearGeneratedSchemas() {
2129
- generatedSchemas.clear();
2130
- }
2131
- async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist) {
2132
- if (generatedSchemas.has(schemaPath)) {
2133
- return false;
2542
+ allowedMethods;
2543
+ };
2544
+ var InternalError = class extends FaapiError {
2545
+ constructor(message) {
2546
+ super("INTERNAL_ERROR", message, 500);
2547
+ this.name = "InternalError";
2134
2548
  }
2135
- const prodAbsPath = path8.resolve(rootDir, routeFilePath);
2136
- const sourceAbsPath = prodPathToSourcePath(prodAbsPath, rootDir, dist);
2137
- if (!fs7.existsSync(sourceAbsPath)) {
2138
- return false;
2549
+ };
2550
+ var ModuleLoadError = class extends FaapiError {
2551
+ constructor(filePath, reason) {
2552
+ super("MODULE_LOAD_ERROR", `Failed to load module ${filePath}: ${reason}`, 500);
2553
+ this.name = "ModuleLoadError";
2139
2554
  }
2140
- if (isProductFresh(sourceAbsPath, schemaPath)) {
2141
- generatedSchemas.add(schemaPath);
2142
- return false;
2555
+ };
2556
+ var PayloadTooLargeError = class extends FaapiError {
2557
+ constructor(maxSize) {
2558
+ super("PAYLOAD_TOO_LARGE", `Request body exceeds size limit of ${maxSize} bytes`, 413);
2559
+ this.name = "PayloadTooLargeError";
2143
2560
  }
2144
- const fileRoutes = routes.filter((r) => r.filePath === routeFilePath);
2145
- if (fileRoutes.length === 0) {
2146
- return false;
2561
+ };
2562
+
2563
+ // src/cli/createAppCore.ts
2564
+ import fs15 from "fs";
2565
+ import path14 from "path";
2566
+ import { PassThrough, Readable as Readable3 } from "stream";
2567
+
2568
+ // src/router/sortRoutes.ts
2569
+ function sortRoutes(routes) {
2570
+ return [...routes].sort((a, b) => {
2571
+ if (a.isDynamic !== b.isDynamic) {
2572
+ return a.isDynamic ? 1 : -1;
2573
+ }
2574
+ if (a.isCatchAll !== b.isCatchAll) {
2575
+ return a.isCatchAll ? 1 : -1;
2576
+ }
2577
+ const aSegments = a.urlPath.split("/").filter(Boolean).length;
2578
+ const bSegments = b.urlPath.split("/").filter(Boolean).length;
2579
+ if (aSegments !== bSegments) {
2580
+ return aSegments - bSegments;
2581
+ }
2582
+ return a.urlPath.localeCompare(b.urlPath);
2583
+ });
2584
+ }
2585
+
2586
+ // src/router/detectRouteConflicts.ts
2587
+ function detectRouteConflicts(routes) {
2588
+ const map = /* @__PURE__ */ new Map();
2589
+ for (const route of routes) {
2590
+ const key = `${route.method} ${route.urlPath}`;
2591
+ const existing = map.get(key);
2592
+ if (existing) {
2593
+ existing.files.push(route.filePath);
2594
+ } else {
2595
+ map.set(key, {
2596
+ method: route.method,
2597
+ urlPath: route.urlPath,
2598
+ files: [route.filePath]
2599
+ });
2600
+ }
2147
2601
  }
2148
- const sourceRelPath = path8.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
2149
- const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
2150
- await generateSchemaFiles(sourceRoutes, rootDir, dist);
2151
- generatedSchemas.add(schemaPath);
2152
- return true;
2602
+ const conflicts = [];
2603
+ for (const conflict of map.values()) {
2604
+ if (conflict.files.length > 1) {
2605
+ conflicts.push(conflict);
2606
+ }
2607
+ }
2608
+ return conflicts;
2153
2609
  }
2154
- async function deleteSchemaFiles(routes, rootDir, dist) {
2155
- const deleted = /* @__PURE__ */ new Set();
2610
+
2611
+ // src/server/createServer.ts
2612
+ import {
2613
+ createServer as createHttpServer
2614
+ } from "http";
2615
+ import { createSecureServer as createHttp2SecureServer } from "http2";
2616
+ import { readFileSync } from "fs";
2617
+ import { Readable as Readable2 } from "stream";
2618
+ import path11 from "path";
2619
+
2620
+ // src/router/matchRoute.ts
2621
+ function matchRoute(routes, method, path18) {
2156
2622
  for (const route of routes) {
2157
- const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);
2158
- if (deleted.has(schemaPath)) continue;
2159
- deleted.add(schemaPath);
2160
- try {
2161
- await fs7.promises.unlink(schemaPath);
2162
- } catch {
2623
+ if (route.method !== method) {
2624
+ continue;
2625
+ }
2626
+ if (!route.isDynamic) {
2627
+ if (route.urlPath === path18) {
2628
+ return { route, params: {} };
2629
+ }
2630
+ continue;
2631
+ }
2632
+ const params = matchDynamicPath(route.urlPath, path18, route.paramNames, route.isCatchAll);
2633
+ if (params !== null) {
2634
+ return { route, params };
2163
2635
  }
2164
2636
  }
2637
+ return null;
2165
2638
  }
2166
- function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
2167
- const rel = path8.relative(rootDir, prodAbsPath).replace(/\\/g, "/");
2168
- let relWithoutDist = rel;
2169
- if (relWithoutDist.startsWith(`${dist}/`)) {
2170
- relWithoutDist = relWithoutDist.slice(dist.length + 1);
2639
+ function matchWsRoute(wsRoutes, path18) {
2640
+ for (const route of wsRoutes) {
2641
+ if (!route.isDynamic) {
2642
+ if (route.urlPath === path18) {
2643
+ return { route, params: {} };
2644
+ }
2645
+ continue;
2646
+ }
2647
+ const params = matchDynamicPath(route.urlPath, path18, route.paramNames, route.isCatchAll);
2648
+ if (params !== null) {
2649
+ return { route, params };
2650
+ }
2171
2651
  }
2172
- const srcRel = `src/${relWithoutDist}`;
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);
2652
+ return null;
2177
2653
  }
2178
- var devOnDemandEnabled = false;
2179
- function isDevOnDemandEnabled() {
2180
- return devOnDemandEnabled;
2654
+ function matchDynamicPath(pattern, path18, paramNames, isCatchAll) {
2655
+ const patternSegments = pattern.split("/").filter(Boolean);
2656
+ const pathSegments = path18.split("/").filter(Boolean);
2657
+ if (isCatchAll) {
2658
+ const nonCatchAllCount = patternSegments.length - 1;
2659
+ if (pathSegments.length <= nonCatchAllCount) {
2660
+ return null;
2661
+ }
2662
+ const params2 = {};
2663
+ for (let i = 0; i < nonCatchAllCount; i++) {
2664
+ const patternSeg = patternSegments[i];
2665
+ const pathSeg = pathSegments[i];
2666
+ if (patternSeg.startsWith(":")) {
2667
+ const paramName = patternSeg.slice(1);
2668
+ params2[paramName] = pathSeg;
2669
+ } else if (patternSeg !== pathSeg) {
2670
+ return null;
2671
+ }
2672
+ }
2673
+ const catchAllValue = pathSegments.slice(nonCatchAllCount).join("/");
2674
+ const catchAllParamName = patternSegments[nonCatchAllCount].slice(4);
2675
+ params2[catchAllParamName] = catchAllValue;
2676
+ if (Object.keys(params2).length !== paramNames.length) {
2677
+ return null;
2678
+ }
2679
+ return params2;
2680
+ }
2681
+ if (patternSegments.length !== pathSegments.length) {
2682
+ return null;
2683
+ }
2684
+ const params = {};
2685
+ for (let i = 0; i < patternSegments.length; i++) {
2686
+ const patternSeg = patternSegments[i];
2687
+ const pathSeg = pathSegments[i];
2688
+ if (patternSeg.startsWith(":")) {
2689
+ const paramName = patternSeg.slice(1);
2690
+ params[paramName] = pathSeg;
2691
+ } else if (patternSeg !== pathSeg) {
2692
+ return null;
2693
+ }
2694
+ }
2695
+ if (Object.keys(params).length !== paramNames.length) {
2696
+ return null;
2697
+ }
2698
+ return params;
2181
2699
  }
2182
- var devDistDir;
2183
- function getDevDist() {
2184
- return devDistDir;
2700
+
2701
+ // src/loader/loadRouteModule.ts
2702
+ import fs11 from "fs";
2703
+
2704
+ // src/loader/validateRouteModule.ts
2705
+ function validateRouteModule(value, method, filePath) {
2706
+ if (typeof value !== "function") {
2707
+ throw new Error(
2708
+ `Route module "${filePath}" does not export a valid handler for method "${method}". Expected a function, got ${typeof value}.`
2709
+ );
2710
+ }
2185
2711
  }
2186
2712
 
2187
2713
  // src/loader/loadRouteModule.ts
@@ -2190,7 +2716,7 @@ async function loadRouteModule(filePath, method, rootDir) {
2190
2716
  const dist = getDevDist();
2191
2717
  if (dist) {
2192
2718
  const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
2193
- if (sourcePath && fs8.existsSync(sourcePath)) {
2719
+ if (sourcePath && fs11.existsSync(sourcePath)) {
2194
2720
  try {
2195
2721
  await ensureCompiled(sourcePath, rootDir, dist);
2196
2722
  } catch (compileErr) {
@@ -2319,6 +2845,92 @@ function createSseWriter() {
2319
2845
  return writer;
2320
2846
  }
2321
2847
 
2848
+ // src/response/responseFormatter.ts
2849
+ function defaultOk(data) {
2850
+ return { data };
2851
+ }
2852
+ function defaultFail(e) {
2853
+ const error = { message: e.message };
2854
+ if (e.code !== void 0) error.code = e.code;
2855
+ return { error };
2856
+ }
2857
+ function getResponseConfig(config) {
2858
+ return config?.response;
2859
+ }
2860
+ function resolveOkFn(config) {
2861
+ return getResponseConfig(config)?.ok ?? defaultOk;
2862
+ }
2863
+ function resolveFailFn(config) {
2864
+ return getResponseConfig(config)?.fail ?? defaultFail;
2865
+ }
2866
+ function jsonOk(body, status = 200, extraHeaders) {
2867
+ return jsonRaw(body, status, extraHeaders);
2868
+ }
2869
+ function jsonRaw(body, status, extraHeaders) {
2870
+ const headers = new Headers({ "Content-Type": "application/json" });
2871
+ if (extraHeaders) {
2872
+ const extra = new Headers(extraHeaders);
2873
+ extra.forEach((value, key) => headers.set(key, value));
2874
+ }
2875
+ return new Response(JSON.stringify(body), { status, headers });
2876
+ }
2877
+ function wrapOkResult(result, config) {
2878
+ if (result instanceof Response) return result;
2879
+ return resolveOkFn(config)(result);
2880
+ }
2881
+ function formatFailResponse(options, config) {
2882
+ const failFn = resolveFailFn(config);
2883
+ const body = failFn({
2884
+ status: options.status,
2885
+ code: options.code,
2886
+ message: options.message
2887
+ });
2888
+ return jsonOk(body, options.status ?? 500);
2889
+ }
2890
+ function formatErrorResponse(error, config) {
2891
+ const failFn = resolveFailFn(config);
2892
+ if (error instanceof ValidationError) {
2893
+ const body2 = failFn({
2894
+ status: error.statusCode,
2895
+ code: error.code,
2896
+ message: error.message
2897
+ });
2898
+ const bodyObj = typeof body2 === "object" && body2 !== null ? body2 : { error: body2 };
2899
+ const errorObj = bodyObj.error ?? bodyObj;
2900
+ if (errorObj) {
2901
+ errorObj.issues = error.issues;
2902
+ }
2903
+ return jsonOk(bodyObj, error.statusCode);
2904
+ }
2905
+ if (error instanceof MethodNotAllowedError) {
2906
+ const body2 = failFn({
2907
+ status: error.statusCode,
2908
+ code: error.code,
2909
+ message: error.message
2910
+ });
2911
+ return jsonOk(body2, error.statusCode, { Allow: error.allowedMethods.join(", ") });
2912
+ }
2913
+ if (error instanceof PayloadTooLargeError) {
2914
+ const body2 = failFn({
2915
+ status: error.statusCode,
2916
+ code: error.code,
2917
+ message: error.message
2918
+ });
2919
+ return jsonOk(body2, error.statusCode);
2920
+ }
2921
+ if (error instanceof FaapiError) {
2922
+ const body2 = failFn({
2923
+ status: error.statusCode,
2924
+ code: error.code,
2925
+ message: error.message
2926
+ });
2927
+ return jsonOk(body2, error.statusCode);
2928
+ }
2929
+ const message = error instanceof Error ? error.message : "An unknown error occurred";
2930
+ const body = failFn({ status: 500, code: "INTERNAL_ERROR", message });
2931
+ return jsonOk(body, 500);
2932
+ }
2933
+
2322
2934
  // src/runtime/createContext.ts
2323
2935
  function parseCookies(cookieHeader) {
2324
2936
  const cookies = /* @__PURE__ */ new Map();
@@ -2379,11 +2991,7 @@ function createContext(request, params, config = {}, ip = "") {
2379
2991
  });
2380
2992
  },
2381
2993
  json(data, status) {
2382
- const headers = { "Content-Type": "application/json" };
2383
- return new Response(JSON.stringify(data), {
2384
- status: status ?? 200,
2385
- headers
2386
- });
2994
+ return jsonOk(data, status ?? 200);
2387
2995
  },
2388
2996
  html(html, status) {
2389
2997
  const headers = { "Content-Type": "text/html; charset=utf-8" };
@@ -2419,18 +3027,22 @@ function createContext(request, params, config = {}, ip = "") {
2419
3027
  /**
2420
3028
  * 显式包装成功响应(返回 Response,不会被自动包裹再次包装)
2421
3029
  *
3030
+ * 实现委托给 [responseFormatter.wrapOkResult](../response/responseFormatter.ts),
3031
+ * 与 handler `return data` 走的自动包裹路径共享同一套 ok 函数。
3032
+ *
2422
3033
  * 用 config.response.ok(或默认 (data) => ({ data })) 包裹 data 并返回 JSON Response。
2423
3034
  * handler 也可直接 return data,框架会自动用 ok 包裹,两者等价。
2424
3035
  */
2425
3036
  ok(data) {
2426
- const responseConfig = config.response;
2427
- const okFn = responseConfig?.ok ?? ((d) => ({ data: d }));
2428
- const body = okFn(data);
2429
- return ctx.json(body);
3037
+ const body = wrapOkResult(data, config);
3038
+ return jsonOk(body, 200);
2430
3039
  },
2431
3040
  /**
2432
3041
  * 返回错误响应(对象形式参数,status 和 code 均可省略)
2433
3042
  *
3043
+ * 实现委托给 [responseFormatter.formatFailResponse](../response/responseFormatter.ts),
3044
+ * 与 formatErrorResponse(handler 抛错兜底)共享同一套 fail 函数,确保错误格式一致。
3045
+ *
2434
3046
  * - status 省略时 HTTP 状态码默认 500
2435
3047
  * - code 省略时响应 body 里不含 code 字段(默认 fail 函数只放非 undefined 的字段)
2436
3048
  * - status 和 code 独立无关联
@@ -2438,18 +3050,7 @@ function createContext(request, params, config = {}, ip = "") {
2438
3050
  * body 用 config.response.fail(或默认实现)包装。
2439
3051
  */
2440
3052
  fail(options) {
2441
- const responseConfig = config.response;
2442
- const failFn = responseConfig?.fail ?? ((e) => {
2443
- const error = { message: e.message };
2444
- if (e.code !== void 0) error.code = e.code;
2445
- return { error };
2446
- });
2447
- const body = failFn({
2448
- status: options.status,
2449
- code: options.code,
2450
- message: options.message
2451
- });
2452
- return ctx.json(body, options.status ?? 500);
3053
+ return formatFailResponse(options, config);
2453
3054
  }
2454
3055
  };
2455
3056
  const extend = config?.extendContext;
@@ -2684,6 +3285,12 @@ function getBuiltinInjectionValue(type, ctx, body) {
2684
3285
  return body.fields;
2685
3286
  }
2686
3287
  return {};
3288
+ // Phase 2.3:注入所有已注册 agent 元数据列表
3289
+ case "agents":
3290
+ return listAgents();
3291
+ // Phase 3.5:调 @faapi/agent 插件注册的工厂获取 AgentHandle
3292
+ case "agent":
3293
+ return getAgentHandle(ctx);
2687
3294
  default:
2688
3295
  return void 0;
2689
3296
  }
@@ -2709,10 +3316,7 @@ async function injectParamsAsync(handler, ctx, body, injectors) {
2709
3316
 
2710
3317
  // src/runtime/invokeHandler.ts
2711
3318
  function wrapResult(result, ctx) {
2712
- if (result instanceof Response) return result;
2713
- const responseConfig = ctx.config.response;
2714
- const okFn = responseConfig?.ok ?? ((d) => ({ data: d }));
2715
- return okFn(result);
3319
+ return wrapOkResult(result, ctx.config);
2716
3320
  }
2717
3321
  function mergeMeta(response, meta) {
2718
3322
  const hasMeta = meta.status !== void 0 || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0;
@@ -2867,9 +3471,9 @@ async function validateInput(schemaPath, method, inputType, input) {
2867
3471
  function mapZodIssues(error) {
2868
3472
  return error.issues.map((issue) => {
2869
3473
  const code = mapZodCode(issue.code, issue.message);
2870
- const path14 = issue.path.map(String).join(".") || "";
3474
+ const path18 = issue.path.map(String).join(".") || "";
2871
3475
  return {
2872
- path: path14,
3476
+ path: path18,
2873
3477
  code,
2874
3478
  expected: issue.expected ?? mapExpectedFromMessage(issue.message),
2875
3479
  received: issue.received ?? mapReceivedFromMessage(issue.message),
@@ -2931,55 +3535,9 @@ function getClientIp(req) {
2931
3535
  }
2932
3536
 
2933
3537
  // src/server/handleWsUpgrade.ts
2934
- import fs9 from "fs";
3538
+ import fs12 from "fs";
2935
3539
  import { WebSocketServer, WebSocket } from "ws";
2936
- import path9 from "path";
2937
-
2938
- // src/errors/formatErrorResponse.ts
2939
- function formatErrorResponse(error) {
2940
- if (error instanceof ValidationError) {
2941
- const body2 = {
2942
- code: error.code,
2943
- message: error.message,
2944
- issues: error.issues
2945
- };
2946
- return new Response(JSON.stringify({ error: body2 }), {
2947
- status: error.statusCode,
2948
- headers: { "Content-Type": "application/json" }
2949
- });
2950
- }
2951
- if (error instanceof MethodNotAllowedError) {
2952
- const body2 = {
2953
- code: error.code,
2954
- message: error.message
2955
- };
2956
- return new Response(JSON.stringify({ error: body2 }), {
2957
- status: error.statusCode,
2958
- headers: {
2959
- "Content-Type": "application/json",
2960
- Allow: error.allowedMethods.join(", ")
2961
- }
2962
- });
2963
- }
2964
- if (error instanceof FaapiError) {
2965
- const body2 = {
2966
- code: error.code,
2967
- message: error.message
2968
- };
2969
- return new Response(JSON.stringify({ error: body2 }), {
2970
- status: error.statusCode,
2971
- headers: { "Content-Type": "application/json" }
2972
- });
2973
- }
2974
- const body = {
2975
- code: "INTERNAL_ERROR",
2976
- message: error instanceof Error ? error.message : "An unknown error occurred"
2977
- };
2978
- return new Response(JSON.stringify({ error: body }), {
2979
- status: 500,
2980
- headers: { "Content-Type": "application/json" }
2981
- });
2982
- }
3540
+ import path10 from "path";
2983
3541
 
2984
3542
  // src/server/serverUtils.ts
2985
3543
  function nodeHttpToWebHeaders(req) {
@@ -2994,9 +3552,9 @@ function nodeHttpToWebHeaders(req) {
2994
3552
  }
2995
3553
  return headers;
2996
3554
  }
2997
- function buildErrorResponse(err) {
3555
+ function buildErrorResponse(err, config) {
2998
3556
  try {
2999
- return formatErrorResponse(err);
3557
+ return formatErrorResponse(err, config);
3000
3558
  } catch {
3001
3559
  return new Response(
3002
3560
  JSON.stringify({ error: { code: "INTERNAL_ERROR", message: "Internal Server Error" } }),
@@ -3020,37 +3578,42 @@ function setCachedMiddlewares(absPath, bundle) {
3020
3578
  middlewareCache.set(absPath, bundle);
3021
3579
  }
3022
3580
  async function loadMiddlewaresFile(filePath) {
3581
+ let module;
3023
3582
  try {
3024
- const module = await importWithCacheBust(filePath);
3025
- const middlewares = module.default ?? module.middlewares ?? [];
3026
- if (!Array.isArray(middlewares)) {
3027
- console.warn(`[faapi] middlewares.ts \u5E94\u5BFC\u51FA\u6570\u7EC4\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
3028
- return { middlewares: [], injectors: {} };
3029
- }
3030
- const validMiddlewares = middlewares.filter((m) => {
3031
- if (typeof m !== "function") {
3032
- console.warn(`[faapi] \u65E0\u6548\u7684\u4E2D\u95F4\u4EF6\u9879\uFF08\u5E94\u4E3A\u51FD\u6570\uFF09\uFF0C\u5DF2\u5FFD\u7565: ${typeof m}`);
3033
- return false;
3034
- }
3035
- return true;
3036
- });
3037
- const injectors = module.injectors ?? {};
3038
- if (typeof injectors !== "object" || injectors === null) {
3039
- console.warn(`[faapi] injectors \u5E94\u5BFC\u51FA\u5BF9\u8C61\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
3040
- return { middlewares: validMiddlewares, injectors: {} };
3041
- }
3042
- const validInjectors = {};
3043
- for (const [name, injector] of Object.entries(injectors)) {
3044
- if (typeof injector !== "function") {
3045
- console.warn(`[faapi] \u6CE8\u5165\u5668 ${name} \u5E94\u4E3A\u51FD\u6570\uFF0C\u5DF2\u5FFD\u7565`);
3046
- continue;
3047
- }
3048
- validInjectors[name] = injector;
3049
- }
3050
- return { middlewares: validMiddlewares, injectors: validInjectors };
3051
- } catch {
3583
+ module = await importWithCacheBust(filePath);
3584
+ } catch (err) {
3585
+ console.error(
3586
+ `[faapi] Failed to load middlewares from ${filePath}:`,
3587
+ err instanceof Error ? err.stack ?? err.message : err
3588
+ );
3052
3589
  return { middlewares: [], injectors: {} };
3053
3590
  }
3591
+ const middlewares = module.default ?? module.middlewares ?? [];
3592
+ if (!Array.isArray(middlewares)) {
3593
+ console.warn(`[faapi] middlewares.ts \u5E94\u5BFC\u51FA\u6570\u7EC4\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
3594
+ return { middlewares: [], injectors: {} };
3595
+ }
3596
+ const validMiddlewares = middlewares.filter((m) => {
3597
+ if (typeof m !== "function") {
3598
+ console.warn(`[faapi] \u65E0\u6548\u7684\u4E2D\u95F4\u4EF6\u9879\uFF08\u5E94\u4E3A\u51FD\u6570\uFF09\uFF0C\u5DF2\u5FFD\u7565: ${typeof m}`);
3599
+ return false;
3600
+ }
3601
+ return true;
3602
+ });
3603
+ const injectors = module.injectors ?? {};
3604
+ if (typeof injectors !== "object" || injectors === null) {
3605
+ console.warn(`[faapi] injectors \u5E94\u5BFC\u51FA\u5BF9\u8C61\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
3606
+ return { middlewares: validMiddlewares, injectors: {} };
3607
+ }
3608
+ const validInjectors = {};
3609
+ for (const [name, injector] of Object.entries(injectors)) {
3610
+ if (typeof injector !== "function") {
3611
+ console.warn(`[faapi] \u6CE8\u5165\u5668 ${name} \u5E94\u4E3A\u51FD\u6570\uFF0C\u5DF2\u5FFD\u7565`);
3612
+ continue;
3613
+ }
3614
+ validInjectors[name] = injector;
3615
+ }
3616
+ return { middlewares: validMiddlewares, injectors: validInjectors };
3054
3617
  }
3055
3618
  async function loadMergedMiddlewares(middlewarePaths) {
3056
3619
  if (middlewarePaths.length === 0) return void 0;
@@ -3100,7 +3663,7 @@ async function loadWsHandler(filePath, ctx, rootDir) {
3100
3663
  const dist = getDevDist();
3101
3664
  if (dist) {
3102
3665
  const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
3103
- if (sourcePath && fs9.existsSync(sourcePath)) {
3666
+ if (sourcePath && fs12.existsSync(sourcePath)) {
3104
3667
  await ensureCompiled(sourcePath, rootDir, dist);
3105
3668
  }
3106
3669
  }
@@ -3180,7 +3743,7 @@ function attachWebSocket(options) {
3180
3743
  const finalHandler = async () => {
3181
3744
  let handlers;
3182
3745
  try {
3183
- const absoluteFilePath = path9.resolve(rootDir, route.filePath);
3746
+ const absoluteFilePath = path10.resolve(rootDir, route.filePath);
3184
3747
  handlers = await loadWsHandler(absoluteFilePath, ctx, rootDir);
3185
3748
  } catch (err) {
3186
3749
  const reason = err instanceof Error ? err.message : String(err);
@@ -3257,37 +3820,63 @@ function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT) {
3257
3820
  }
3258
3821
  function limitStreamSize(stream, maxSize) {
3259
3822
  let totalSize = 0;
3260
- const reader = stream.getReader();
3261
- return new ReadableStream({
3262
- async pull(controller) {
3263
- const { done, value } = await reader.read();
3264
- if (done) {
3265
- controller.close();
3823
+ let reader;
3824
+ let errored = false;
3825
+ const releaseReader = () => {
3826
+ if (reader) {
3827
+ try {
3266
3828
  reader.releaseLock();
3267
- return;
3829
+ } catch {
3268
3830
  }
3269
- totalSize += value.byteLength;
3270
- if (totalSize > maxSize) {
3271
- controller.error(new Error(`\u8BF7\u6C42\u4F53\u8D85\u8FC7\u5927\u5C0F\u9650\u5236 ${maxSize} \u5B57\u8282`));
3272
- reader.releaseLock();
3273
- return;
3831
+ reader = void 0;
3832
+ }
3833
+ };
3834
+ const failStream = (controller, err) => {
3835
+ if (errored) return;
3836
+ errored = true;
3837
+ controller.error(err instanceof Error ? err : new Error(String(err)));
3838
+ releaseReader();
3839
+ };
3840
+ return new ReadableStream({
3841
+ async pull(controller) {
3842
+ if (!reader) reader = stream.getReader();
3843
+ try {
3844
+ const { done, value } = await reader.read();
3845
+ if (done) {
3846
+ controller.close();
3847
+ releaseReader();
3848
+ return;
3849
+ }
3850
+ totalSize += value.byteLength;
3851
+ if (totalSize > maxSize) {
3852
+ failStream(controller, new PayloadTooLargeError(maxSize));
3853
+ return;
3854
+ }
3855
+ controller.enqueue(value);
3856
+ } catch (err) {
3857
+ failStream(controller, err);
3274
3858
  }
3275
- controller.enqueue(value);
3276
3859
  },
3277
3860
  cancel(reason) {
3278
- reader.cancel(reason);
3861
+ if (reader) {
3862
+ try {
3863
+ reader.cancel(reason);
3864
+ } catch {
3865
+ }
3866
+ releaseReader();
3867
+ }
3279
3868
  }
3280
3869
  });
3281
3870
  }
3282
- function findAllowedMethods(routes, path14) {
3871
+ function findAllowedMethods(routes, path18) {
3283
3872
  const methods = /* @__PURE__ */ new Set();
3284
3873
  for (const route of routes) {
3285
- if (route.urlPath === path14) {
3874
+ if (route.urlPath === path18) {
3286
3875
  methods.add(route.method);
3287
3876
  continue;
3288
3877
  }
3289
3878
  if (route.isDynamic) {
3290
- const params = matchDynamicPath(route.urlPath, path14, route.paramNames, route.isCatchAll);
3879
+ const params = matchDynamicPath(route.urlPath, path18, route.paramNames, route.isCatchAll);
3291
3880
  if (params !== null) {
3292
3881
  methods.add(route.method);
3293
3882
  }
@@ -3356,24 +3945,30 @@ function createServer(options) {
3356
3945
  }
3357
3946
  return { server, routesRef };
3358
3947
  }
3359
- async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares, onError, config, globalMiddlewares, globalInjectors, bodyLimit) {
3948
+ function prepareRequest(req, config, bodyLimit) {
3360
3949
  const request = toWebRequest(req, bodyLimit);
3361
3950
  const method = request.method.toUpperCase();
3362
3951
  const urlPath = new URL(request.url).pathname;
3363
3952
  const ctx = createContext(request, {}, config, getClientIp(req));
3364
3953
  const meta = ctx.meta;
3365
- const routePipeline = async () => {
3366
- const match = matchRoute(routes, method, urlPath);
3367
- if (!match) {
3368
- const allowedMethods = findAllowedMethods(routes, urlPath);
3369
- if (allowedMethods.length > 0) {
3370
- throw new MethodNotAllowedError(method, urlPath, allowedMethods);
3371
- }
3372
- throw new RouteNotFoundError(urlPath);
3373
- }
3954
+ return { request, ctx, meta, method, urlPath };
3955
+ }
3956
+ function resolveRouteOrThrow(routes, method, urlPath) {
3957
+ const match = matchRoute(routes, method, urlPath);
3958
+ if (match) return match;
3959
+ const allowedMethods = findAllowedMethods(routes, urlPath);
3960
+ if (allowedMethods.length > 0) {
3961
+ throw new MethodNotAllowedError(method, urlPath, allowedMethods);
3962
+ }
3963
+ throw new RouteNotFoundError(urlPath);
3964
+ }
3965
+ function createRoutePipeline(opts) {
3966
+ const { routes, method, urlPath, ctx, request, rootDir, dist, globalInjectors } = opts;
3967
+ return async () => {
3968
+ const match = resolveRouteOrThrow(routes, method, urlPath);
3374
3969
  ctx.params = match.params;
3375
3970
  const { route } = match;
3376
- const absoluteFilePath = path10.resolve(rootDir, route.filePath);
3971
+ const absoluteFilePath = path11.resolve(rootDir, route.filePath);
3377
3972
  const routeModule = await loadRouteModule(absoluteFilePath, route.method, rootDir);
3378
3973
  const input = await resolveInput(route.method, request);
3379
3974
  const inputType = getInputTypeForMethod(route.method);
@@ -3400,37 +3995,44 @@ async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares,
3400
3995
  }
3401
3996
  }
3402
3997
  const mergedInjectors = globalInjectors ? { ...globalInjectors, ...route.injectors } : route.injectors;
3403
- const response = await invokeHandler(
3404
- routeModule.handler,
3405
- ctx,
3406
- body,
3407
- route.middlewares,
3408
- mergedInjectors
3409
- );
3410
- return response;
3998
+ return await invokeHandler(routeModule.handler, ctx, body, route.middlewares, mergedInjectors);
3411
3999
  };
3412
- try {
3413
- let response;
3414
- const outerMiddlewares = [];
3415
- if (configMiddlewares.length > 0) outerMiddlewares.push(...configMiddlewares);
3416
- if (globalMiddlewares && globalMiddlewares.length > 0) {
3417
- outerMiddlewares.push(...globalMiddlewares);
3418
- }
3419
- if (outerMiddlewares.length > 0) {
3420
- response = await compose(outerMiddlewares, ctx, routePipeline);
3421
- } else {
3422
- response = await routePipeline();
4000
+ }
4001
+ async function sendSuccessResponse(response, res) {
4002
+ await sendNodeResponse(response, res);
4003
+ }
4004
+ async function sendErrorResponse(err, meta, res, onError, ctx) {
4005
+ await sendNodeResponse(mergeMeta(buildErrorResponse(err, ctx.config), meta), res);
4006
+ if (onError) {
4007
+ try {
4008
+ await onError(err, ctx);
4009
+ } catch {
3423
4010
  }
3424
- await sendNodeResponse(response, res);
4011
+ }
4012
+ }
4013
+ async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares, onError, config, globalMiddlewares, globalInjectors, bodyLimit) {
4014
+ const { request, ctx, meta, method, urlPath } = prepareRequest(req, config, bodyLimit);
4015
+ const routePipeline = createRoutePipeline({
4016
+ routes,
4017
+ method,
4018
+ urlPath,
4019
+ ctx,
4020
+ request,
4021
+ rootDir,
4022
+ dist,
4023
+ globalMiddlewares,
4024
+ globalInjectors
4025
+ });
4026
+ const outerMiddlewares = [];
4027
+ if (configMiddlewares.length > 0) outerMiddlewares.push(...configMiddlewares);
4028
+ if (globalMiddlewares && globalMiddlewares.length > 0) {
4029
+ outerMiddlewares.push(...globalMiddlewares);
4030
+ }
4031
+ try {
4032
+ const response = outerMiddlewares.length > 0 ? await compose(outerMiddlewares, ctx, routePipeline) : await routePipeline();
4033
+ await sendSuccessResponse(response, res);
3425
4034
  } catch (err) {
3426
- const errorResponse = buildErrorResponse(err);
3427
- await sendNodeResponse(mergeMeta(errorResponse, meta), res);
3428
- if (onError) {
3429
- try {
3430
- await onError(err, ctx);
3431
- } catch {
3432
- }
3433
- }
4035
+ await sendErrorResponse(err, meta, res, onError, ctx);
3434
4036
  }
3435
4037
  }
3436
4038
 
@@ -3463,8 +4065,8 @@ function applyPluginWrappers(server, handlerWrappers, upgradeWrappers) {
3463
4065
  }
3464
4066
 
3465
4067
  // src/cli/generateRoutes.ts
3466
- import fs10 from "fs";
3467
- import path11 from "path";
4068
+ import fs13 from "fs";
4069
+ import path12 from "path";
3468
4070
  async function hydrateRoutes(manifest) {
3469
4071
  const hydrateRoute = (serialized) => ({
3470
4072
  method: serialized.method,
@@ -3488,6 +4090,262 @@ async function hydrateRoutes(manifest) {
3488
4090
  return { routes, wsRoutes };
3489
4091
  }
3490
4092
 
4093
+ // src/cli/generateAgentArtifacts.ts
4094
+ import path13 from "path";
4095
+ import fs14 from "fs/promises";
4096
+
4097
+ // src/ast/extractAgentMetadata.ts
4098
+ import ts8 from "typescript";
4099
+ function extractAgentMetadata(program, filePath, pathMeta) {
4100
+ const sourceFile = program.getSourceFile(filePath);
4101
+ if (!sourceFile) return null;
4102
+ const configFound = findConfigExport(sourceFile);
4103
+ let jsDocOwner = null;
4104
+ let objectLiteral = null;
4105
+ if (configFound) {
4106
+ jsDocOwner = configFound.jsDocOwner;
4107
+ objectLiteral = configFound.objectLiteral;
4108
+ } else if (pathMeta.hasRun) {
4109
+ const runNode = findRunExport(sourceFile);
4110
+ if (runNode) {
4111
+ jsDocOwner = runNode;
4112
+ }
4113
+ }
4114
+ const jsDoc = jsDocOwner ? getJSDocFromNode2(jsDocOwner) : void 0;
4115
+ const description = extractDescription2(jsDoc);
4116
+ const agentNameOverride = extractAgentTagValue(jsDoc);
4117
+ let systemPrompt;
4118
+ let tools;
4119
+ let agents;
4120
+ let model;
4121
+ let maxTurns;
4122
+ if (objectLiteral) {
4123
+ const fields = extractConfigFields(objectLiteral);
4124
+ systemPrompt = fields.systemPrompt;
4125
+ tools = fields.tools;
4126
+ agents = fields.agents;
4127
+ model = fields.model;
4128
+ maxTurns = fields.maxTurns;
4129
+ }
4130
+ return {
4131
+ name: agentNameOverride ?? pathMeta.name,
4132
+ description,
4133
+ filePath: pathMeta.filePath,
4134
+ hasRun: pathMeta.hasRun,
4135
+ systemPrompt,
4136
+ tools,
4137
+ agents,
4138
+ model,
4139
+ maxTurns
4140
+ };
4141
+ }
4142
+ function findConfigExport(sourceFile) {
4143
+ let result = null;
4144
+ ts8.forEachChild(sourceFile, (node) => {
4145
+ if (result) return;
4146
+ if (ts8.isVariableStatement(node) && hasExportModifier2(node)) {
4147
+ for (const decl of node.declarationList.declarations) {
4148
+ if (result) break;
4149
+ const nameText = ts8.isIdentifier(decl.name) ? decl.name.text : "";
4150
+ if (nameText !== "config" || !decl.initializer) continue;
4151
+ if (ts8.isObjectLiteralExpression(decl.initializer)) {
4152
+ result = { jsDocOwner: node, objectLiteral: decl.initializer };
4153
+ } else if (ts8.isArrowFunction(decl.initializer)) {
4154
+ const returnObj = getReturnObjectLiteral(decl.initializer);
4155
+ result = { jsDocOwner: node, objectLiteral: returnObj };
4156
+ }
4157
+ }
4158
+ }
4159
+ if (ts8.isFunctionDeclaration(node) && hasExportModifier2(node) && node.name?.text === "config") {
4160
+ const returnObj = getReturnObjectLiteral(node);
4161
+ result = { jsDocOwner: node, objectLiteral: returnObj };
4162
+ }
4163
+ });
4164
+ return result;
4165
+ }
4166
+ function findRunExport(sourceFile) {
4167
+ let result = null;
4168
+ ts8.forEachChild(sourceFile, (node) => {
4169
+ if (result) return;
4170
+ if (ts8.isFunctionDeclaration(node) && hasExportModifier2(node) && node.name?.text === "run") {
4171
+ result = node;
4172
+ return;
4173
+ }
4174
+ if (ts8.isVariableStatement(node) && hasExportModifier2(node)) {
4175
+ for (const decl of node.declarationList.declarations) {
4176
+ if (result) break;
4177
+ const nameText = ts8.isIdentifier(decl.name) ? decl.name.text : "";
4178
+ if (nameText !== "run" || !decl.initializer) continue;
4179
+ if (ts8.isArrowFunction(decl.initializer) || ts8.isFunctionExpression(decl.initializer)) {
4180
+ result = node;
4181
+ }
4182
+ }
4183
+ }
4184
+ });
4185
+ return result;
4186
+ }
4187
+ function getReturnObjectLiteral(fn) {
4188
+ const body = fn.body;
4189
+ if (!body) return null;
4190
+ if (ts8.isObjectLiteralExpression(body)) {
4191
+ return body;
4192
+ }
4193
+ if (ts8.isBlock(body)) {
4194
+ for (const stmt of body.statements) {
4195
+ if (ts8.isReturnStatement(stmt) && stmt.expression && ts8.isObjectLiteralExpression(stmt.expression)) {
4196
+ return stmt.expression;
4197
+ }
4198
+ }
4199
+ }
4200
+ return null;
4201
+ }
4202
+ function hasExportModifier2(node) {
4203
+ if (!ts8.canHaveModifiers(node)) return false;
4204
+ const modifiers = ts8.getModifiers(node);
4205
+ return !!modifiers?.some((m) => m.kind === ts8.SyntaxKind.ExportKeyword);
4206
+ }
4207
+ function getJSDocFromNode2(node) {
4208
+ const apiDocs = ts8.getJSDocCommentsAndTags(node).filter((entry) => ts8.isJSDoc(entry));
4209
+ if (apiDocs.length > 0) return apiDocs[0];
4210
+ const directDocs = node.jsDoc;
4211
+ if (directDocs && directDocs.length > 0) return directDocs[0];
4212
+ return void 0;
4213
+ }
4214
+ function extractDescription2(jsDoc) {
4215
+ if (!jsDoc) return void 0;
4216
+ if (typeof jsDoc.comment !== "string") return void 0;
4217
+ const trimmed = jsDoc.comment.trim();
4218
+ return trimmed || void 0;
4219
+ }
4220
+ function extractAgentTagValue(jsDoc) {
4221
+ if (!jsDoc || !jsDoc.tags) return void 0;
4222
+ for (const tag of jsDoc.tags) {
4223
+ if (tag.tagName.text !== "agent") continue;
4224
+ if (typeof tag.comment !== "string") return void 0;
4225
+ const text = tag.comment.trim();
4226
+ if (!text) return void 0;
4227
+ const cleaned = text.replace(/^\{|\}$/g, "").trim();
4228
+ return cleaned || void 0;
4229
+ }
4230
+ return void 0;
4231
+ }
4232
+ function extractConfigFields(objLit) {
4233
+ const result = {};
4234
+ for (const prop of objLit.properties) {
4235
+ if (!ts8.isPropertyAssignment(prop)) continue;
4236
+ const propName = getPropertyName(prop.name);
4237
+ if (!propName) continue;
4238
+ switch (propName) {
4239
+ case "systemPrompt":
4240
+ result.systemPrompt = extractStringValue(prop.initializer);
4241
+ break;
4242
+ case "tools":
4243
+ result.tools = extractStringArrayValue(prop.initializer);
4244
+ break;
4245
+ case "agents":
4246
+ result.agents = extractStringArrayValue(prop.initializer);
4247
+ break;
4248
+ case "model":
4249
+ result.model = extractStringValue(prop.initializer);
4250
+ break;
4251
+ case "maxTurns":
4252
+ result.maxTurns = extractNumberValue(prop.initializer);
4253
+ break;
4254
+ }
4255
+ }
4256
+ return result;
4257
+ }
4258
+ function getPropertyName(name) {
4259
+ if (ts8.isIdentifier(name)) return name.text;
4260
+ if (ts8.isStringLiteral(name)) return name.text;
4261
+ return null;
4262
+ }
4263
+ function extractStringValue(expr) {
4264
+ if (ts8.isStringLiteral(expr)) return expr.text;
4265
+ return void 0;
4266
+ }
4267
+ function extractNumberValue(expr) {
4268
+ if (ts8.isNumericLiteral(expr)) {
4269
+ const num = Number(expr.text);
4270
+ return Number.isNaN(num) ? void 0 : num;
4271
+ }
4272
+ return void 0;
4273
+ }
4274
+ function extractStringArrayValue(expr) {
4275
+ if (!ts8.isArrayLiteralExpression(expr)) return void 0;
4276
+ const values = [];
4277
+ for (const element of expr.elements) {
4278
+ if (!ts8.isStringLiteral(element)) return void 0;
4279
+ values.push(element.text);
4280
+ }
4281
+ return values;
4282
+ }
4283
+
4284
+ // src/cli/generateAgentArtifacts.ts
4285
+ init_createProgram();
4286
+ var AGENTS_FILE = "faapi-agents.js";
4287
+ function toProdFilePath2(filePath, dist) {
4288
+ let rel = filePath.replace(/\\/g, "/");
4289
+ if (rel.startsWith("src/")) {
4290
+ rel = rel.slice(4);
4291
+ }
4292
+ const jsPath = rel.replace(/\.ts$/, ".js");
4293
+ return jsPath.startsWith(`${dist}/`) ? jsPath : `${dist}/${jsPath}`;
4294
+ }
4295
+ function serializeAgents(agents, dist = "dist") {
4296
+ return agents.map((a) => ({
4297
+ name: a.name,
4298
+ description: a.description,
4299
+ hasRun: a.hasRun,
4300
+ systemPrompt: a.systemPrompt,
4301
+ tools: a.tools,
4302
+ agents: a.agents,
4303
+ model: a.model,
4304
+ maxTurns: a.maxTurns,
4305
+ filePath: toProdFilePath2(a.filePath, dist)
4306
+ }));
4307
+ }
4308
+ async function writeAgentsModule(manifest, outputPath) {
4309
+ const dir = path13.dirname(outputPath);
4310
+ await fs14.mkdir(dir, { recursive: true });
4311
+ const content = `// \u81EA\u52A8\u751F\u6210,\u8BF7\u52FF\u624B\u52A8\u7F16\u8F91(faapi build/dev \u4EA7\u7269)
4312
+ export const agents = ${JSON.stringify(manifest, null, 2)};
4313
+ `;
4314
+ await fs14.writeFile(outputPath, content, "utf-8");
4315
+ }
4316
+ function hydrateAgents(manifest) {
4317
+ return manifest.map((a) => ({
4318
+ name: a.name,
4319
+ description: a.description ?? void 0,
4320
+ filePath: a.filePath,
4321
+ hasRun: a.hasRun,
4322
+ systemPrompt: a.systemPrompt ?? void 0,
4323
+ tools: a.tools ?? void 0,
4324
+ agents: a.agents ?? void 0,
4325
+ model: a.model ?? void 0,
4326
+ maxTurns: a.maxTurns ?? void 0
4327
+ }));
4328
+ }
4329
+ async function generateAgentArtifacts(agents, rootDir, dist) {
4330
+ const metadata = [];
4331
+ for (const manifest of agents) {
4332
+ const absPath = path13.resolve(rootDir, manifest.filePath);
4333
+ const program = createProgram(absPath);
4334
+ const result = extractAgentMetadata(program, absPath, {
4335
+ name: manifest.name,
4336
+ filePath: manifest.filePath,
4337
+ hasRun: manifest.hasRun
4338
+ });
4339
+ if (result) {
4340
+ metadata.push(result);
4341
+ }
4342
+ }
4343
+ const serialized = serializeAgents(metadata, dist);
4344
+ const agentsPath = path13.resolve(rootDir, dist, AGENTS_FILE);
4345
+ await writeAgentsModule(serialized, agentsPath);
4346
+ return metadata;
4347
+ }
4348
+
3491
4349
  // src/cli/loadPlugins.ts
3492
4350
  async function loadPlugins(declarations, ctx) {
3493
4351
  const handlerWrappers = [];
@@ -3551,7 +4409,29 @@ function resolveDeclaration(decl) {
3551
4409
  var DEFAULT_DIST = "dist";
3552
4410
  var DEFAULT_PORT = 3e3;
3553
4411
  var ROUTES_FILE = "faapi-routes.js";
4412
+ var TOOLS_FILE2 = "faapi-tools.js";
4413
+ var AGENTS_FILE2 = "faapi-agents.js";
3554
4414
  var PATTERNS = ["src/api/**/*.ts"];
4415
+ async function loadAndHydrateTools(rootDir, dist) {
4416
+ const toolsPath = path14.resolve(rootDir, dist, TOOLS_FILE2);
4417
+ if (!fs15.existsSync(toolsPath)) {
4418
+ return [];
4419
+ }
4420
+ const serialized = await importWithCacheBust(toolsPath);
4421
+ const hydrated = hydrateTools(serialized.tools ?? []);
4422
+ hydrateToolRegistry(hydrated);
4423
+ return hydrated;
4424
+ }
4425
+ async function loadAndHydrateAgents(rootDir, dist) {
4426
+ const agentsPath = path14.resolve(rootDir, dist, AGENTS_FILE2);
4427
+ if (!fs15.existsSync(agentsPath)) {
4428
+ return [];
4429
+ }
4430
+ const serialized = await importWithCacheBust(agentsPath);
4431
+ const hydrated = hydrateAgents(serialized.agents ?? []);
4432
+ hydrateAgentRegistry(hydrated);
4433
+ return hydrated;
4434
+ }
3555
4435
  var APP_INSTANCE_KEY = /* @__PURE__ */ Symbol.for("faapi.app.instance");
3556
4436
  function getCurrentApp() {
3557
4437
  return globalThis[APP_INSTANCE_KEY] ?? null;
@@ -3591,8 +4471,8 @@ function isFaapiConfigKey(key) {
3591
4471
  async function createAppBase(options) {
3592
4472
  const rootDir = options?.rootDir ?? process.cwd();
3593
4473
  const dist = options?.dist ?? process.env.FAAPI_DIST ?? DEFAULT_DIST;
3594
- const routesPath = path12.resolve(rootDir, dist, ROUTES_FILE);
3595
- if (!fs11.existsSync(routesPath)) {
4474
+ const routesPath = path14.resolve(rootDir, dist, ROUTES_FILE);
4475
+ if (!fs15.existsSync(routesPath)) {
3596
4476
  throw new Error(
3597
4477
  `[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
4478
  );
@@ -3611,6 +4491,8 @@ async function createAppBase(options) {
3611
4491
  }
3612
4492
  }
3613
4493
  }
4494
+ const tools = await loadAndHydrateTools(rootDir, dist);
4495
+ const agents = await loadAndHydrateAgents(rootDir, dist);
3614
4496
  const pluginConfig = config ? Object.fromEntries(Object.entries(config).filter(([k]) => !isFaapiConfigKey(k))) : {};
3615
4497
  const { server, routesRef } = createServer({
3616
4498
  routes: sorted,
@@ -3660,6 +4542,19 @@ async function createAppBase(options) {
3660
4542
  console.log(` WS ${route.urlPath} ${route.filePath}`);
3661
4543
  }
3662
4544
  }
4545
+ if (tools.length > 0) {
4546
+ console.log(`- Loaded ${tools.length} tool(s):`);
4547
+ for (const tool of tools) {
4548
+ console.log(` ${tool.name} ${tool.filePath}`);
4549
+ }
4550
+ }
4551
+ if (agents.length > 0) {
4552
+ console.log(`- Loaded ${agents.length} agent(s):`);
4553
+ for (const agent of agents) {
4554
+ const exports = agent.hasRun ? "run" : "-";
4555
+ console.log(` ${agent.name} [${exports}] ${agent.filePath}`);
4556
+ }
4557
+ }
3663
4558
  if (config?.lifecycle?.onClose) {
3664
4559
  const graceful = async (signal) => {
3665
4560
  console.log(`
@@ -3757,6 +4652,10 @@ async function createAppBase(options) {
3757
4652
  if (config?.lifecycle?.onClose) {
3758
4653
  await config.lifecycle.onClose({ rootDir, routes: sorted, server });
3759
4654
  }
4655
+ clearToolRegistry();
4656
+ clearAgentRegistry();
4657
+ clearSkillRegistry();
4658
+ clearAgentHandleFactory();
3760
4659
  if (!server.listening) {
3761
4660
  app.server = null;
3762
4661
  if (getCurrentApp() === app) setCurrentApp(null);
@@ -3794,17 +4693,17 @@ async function createAppBase(options) {
3794
4693
 
3795
4694
  // src/router/scanRoutes.ts
3796
4695
  import fg2 from "fast-glob";
3797
- import path13 from "path";
3798
- import fs12 from "fs";
4696
+ import path15 from "path";
4697
+ import fs16 from "fs";
3799
4698
 
3800
4699
  // src/router/constants.ts
3801
4700
  var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
3802
4701
  var HTTP_METHOD_SET = new Set(HTTP_METHODS);
3803
4702
 
3804
4703
  // src/utils/normalizePath.ts
3805
- function normalizePath(path14) {
3806
- if (!path14) return "";
3807
- let result = path14.replace(/\\/g, "/");
4704
+ function normalizePath(path18) {
4705
+ if (!path18) return "";
4706
+ let result = path18.replace(/\\/g, "/");
3808
4707
  result = result.replace(/\/+/g, "/");
3809
4708
  result = result.replace(/\/+$/, "");
3810
4709
  if (result && !result.startsWith("/")) {
@@ -3865,41 +4764,41 @@ function extractExportsFromSource(source) {
3865
4764
  return names;
3866
4765
  }
3867
4766
  function collectMiddlewarePaths(routeFilePath, rootDir, dist) {
3868
- const routeDir = path13.dirname(routeFilePath);
3869
- const resolvedRoot = path13.resolve(rootDir);
4767
+ const routeDir = path15.dirname(routeFilePath);
4768
+ const resolvedRoot = path15.resolve(rootDir);
3870
4769
  const paths = [];
3871
- let currentDir = path13.resolve(rootDir, routeDir);
4770
+ let currentDir = path15.resolve(rootDir, routeDir);
3872
4771
  while (true) {
3873
4772
  if (dist) {
3874
- const mwTsPath = path13.join(currentDir, "middlewares.ts");
3875
- const mwJsPath = path13.join(currentDir, "middlewares.js");
3876
- const absTsPath = path13.resolve(rootDir, mwTsPath);
3877
- const absJsPath = path13.resolve(rootDir, mwJsPath);
3878
- const absMwPath = fs12.existsSync(absTsPath) ? absTsPath : fs12.existsSync(absJsPath) ? absJsPath : null;
4773
+ const mwTsPath = path15.join(currentDir, "middlewares.ts");
4774
+ const mwJsPath = path15.join(currentDir, "middlewares.js");
4775
+ const absTsPath = path15.resolve(rootDir, mwTsPath);
4776
+ const absJsPath = path15.resolve(rootDir, mwJsPath);
4777
+ const absMwPath = fs16.existsSync(absTsPath) ? absTsPath : fs16.existsSync(absJsPath) ? absJsPath : null;
3879
4778
  if (absMwPath) {
3880
- const relMwPath = path13.relative(rootDir, absMwPath);
3881
- const prodAbsPath = path13.resolve(rootDir, toProdFilePath(relMwPath, dist));
4779
+ const relMwPath = path15.relative(rootDir, absMwPath);
4780
+ const prodAbsPath = path15.resolve(rootDir, toProdFilePath3(relMwPath, dist));
3882
4781
  paths.push(prodAbsPath);
3883
4782
  }
3884
4783
  } else {
3885
4784
  for (const ext of [".ts", ".js"]) {
3886
- const mwPath = path13.join(currentDir, `middlewares${ext}`);
3887
- const absMwPath = path13.resolve(rootDir, mwPath);
3888
- if (fs12.existsSync(absMwPath)) {
4785
+ const mwPath = path15.join(currentDir, `middlewares${ext}`);
4786
+ const absMwPath = path15.resolve(rootDir, mwPath);
4787
+ if (fs16.existsSync(absMwPath)) {
3889
4788
  paths.push(absMwPath);
3890
4789
  break;
3891
4790
  }
3892
4791
  }
3893
4792
  }
3894
4793
  if (currentDir === resolvedRoot) break;
3895
- const parentDir = path13.dirname(currentDir);
4794
+ const parentDir = path15.dirname(currentDir);
3896
4795
  if (parentDir === currentDir) break;
3897
4796
  currentDir = parentDir;
3898
4797
  }
3899
4798
  paths.reverse();
3900
4799
  return paths;
3901
4800
  }
3902
- function toProdFilePath(filePath, dist) {
4801
+ function toProdFilePath3(filePath, dist) {
3903
4802
  let rel = filePath.replace(/\\/g, "/");
3904
4803
  if (rel.startsWith("src/")) {
3905
4804
  rel = rel.slice(4);
@@ -3919,7 +4818,7 @@ async function scanRoutes(rootDir, patterns, dist) {
3919
4818
  const normalizedFile = file.replace(/\\/g, "/");
3920
4819
  const fileName = normalizedFile.split("/").pop();
3921
4820
  if (fileName === "handler.ts" || fileName === "handler.js") {
3922
- const absPath = path13.resolve(rootDir, normalizedFile);
4821
+ const absPath = path15.resolve(rootDir, normalizedFile);
3923
4822
  const urlPath = filePathToUrlPath(normalizedFile);
3924
4823
  const paramNames = extractParamNames(urlPath);
3925
4824
  const isDynamic = paramNames.length > 0;
@@ -3932,7 +4831,7 @@ async function scanRoutes(rootDir, patterns, dist) {
3932
4831
  const mwPaths = collectMiddlewarePaths(normalizedFile, rootDir);
3933
4832
  middlewareBundle = await loadMergedMiddlewares(mwPaths);
3934
4833
  }
3935
- const source = await fs12.promises.readFile(absPath, "utf8").catch(() => "");
4834
+ const source = await fs16.promises.readFile(absPath, "utf8").catch(() => "");
3936
4835
  const exportNames = extractExportsFromSource(source);
3937
4836
  const methods = HTTP_METHODS.filter((m) => exportNames.has(m));
3938
4837
  for (const method of methods) {
@@ -3966,6 +4865,137 @@ async function scanRoutes(rootDir, patterns, dist) {
3966
4865
  return { routes, wsRoutes };
3967
4866
  }
3968
4867
 
4868
+ // src/tools/scanTools.ts
4869
+ import fg3 from "fast-glob";
4870
+ import path16 from "path";
4871
+ import fs17 from "fs";
4872
+ var TOOL_PATTERNS = ["src/tools/**/*.ts"];
4873
+ var TOOL_EXPORT_RE = new RegExp(
4874
+ String.raw`export\s+(?:async\s+)?(?:function\s+|const\s+)([A-Za-z_$][\w$]*)\s*(?:\(|=)`,
4875
+ "g"
4876
+ );
4877
+ var RESERVED_EXPORTS = /* @__PURE__ */ new Set(["default", "config", "run"]);
4878
+ function extractToolExportsFromSource(source) {
4879
+ const names = /* @__PURE__ */ new Set();
4880
+ let match;
4881
+ TOOL_EXPORT_RE.lastIndex = 0;
4882
+ while ((match = TOOL_EXPORT_RE.exec(source)) !== null) {
4883
+ const name = match[1];
4884
+ if (!RESERVED_EXPORTS.has(name)) {
4885
+ names.add(name);
4886
+ }
4887
+ }
4888
+ return names;
4889
+ }
4890
+ function extractNamespaceFromRelPath(relPath) {
4891
+ const lastSlash = relPath.lastIndexOf("/");
4892
+ const dirPath = lastSlash === -1 ? "" : relPath.slice(0, lastSlash);
4893
+ if (!dirPath) return "";
4894
+ return dirPath.split("/").join(".");
4895
+ }
4896
+ function filePathToToolNamespace(filePath) {
4897
+ const normalized = filePath.replace(/\\/g, "/");
4898
+ const toolsMatch = normalized.match(/(?:^|\/)tools\/(.+)$/);
4899
+ if (toolsMatch) {
4900
+ return extractNamespaceFromRelPath(toolsMatch[1]);
4901
+ }
4902
+ return "";
4903
+ }
4904
+ function buildToolName(namespace, functionName) {
4905
+ return namespace ? `${namespace}.${functionName}` : functionName;
4906
+ }
4907
+ async function scanTools(rootDir, patterns) {
4908
+ const files = await fg3(patterns, {
4909
+ cwd: rootDir,
4910
+ onlyFiles: true,
4911
+ absolute: false
4912
+ });
4913
+ const tools = [];
4914
+ const seen = /* @__PURE__ */ new Map();
4915
+ for (const file of files) {
4916
+ const normalizedFile = file.replace(/\\/g, "/");
4917
+ const fileName = normalizedFile.split("/").pop();
4918
+ if (fileName !== "handler.ts" && fileName !== "handler.js") {
4919
+ continue;
4920
+ }
4921
+ const absPath = path16.resolve(rootDir, normalizedFile);
4922
+ const source = await fs17.promises.readFile(absPath, "utf8").catch(() => "");
4923
+ const exportNames = extractToolExportsFromSource(source);
4924
+ const namespace = filePathToToolNamespace(normalizedFile);
4925
+ for (const fnName of exportNames) {
4926
+ const toolName = buildToolName(namespace, fnName);
4927
+ const prevFile = seen.get(toolName);
4928
+ if (prevFile) {
4929
+ throw new Error(
4930
+ `Tool conflict: "${toolName}" declared in both ${prevFile} and ${normalizedFile}`
4931
+ );
4932
+ }
4933
+ seen.set(toolName, normalizedFile);
4934
+ tools.push({
4935
+ name: toolName,
4936
+ functionName: fnName,
4937
+ filePath: normalizedFile
4938
+ });
4939
+ }
4940
+ }
4941
+ return tools;
4942
+ }
4943
+
4944
+ // src/agents/scanAgents.ts
4945
+ import fg4 from "fast-glob";
4946
+ import path17 from "path";
4947
+ import fs18 from "fs";
4948
+ var DEFAULT_AGENT_PATTERNS = ["src/agents/*/handler.ts"];
4949
+ var RUN_EXPORT_RE = /export\s+(?:async\s+)?(?:function\s+|const\s+)run\b/;
4950
+ function extractAgentNameFromPath(filePath) {
4951
+ const normalized = filePath.replace(/\\/g, "/");
4952
+ const match = normalized.match(/(?:^|\/)agents\/([^/]+)\/handler\.ts$/);
4953
+ if (!match) {
4954
+ throw new Error(
4955
+ `Not an agent path: "${filePath}". Expected pattern: src/agents/<name>/handler.ts`
4956
+ );
4957
+ }
4958
+ return match[1];
4959
+ }
4960
+ function detectAgentExports(source) {
4961
+ return {
4962
+ hasRun: RUN_EXPORT_RE.test(source)
4963
+ };
4964
+ }
4965
+ async function scanAgents(rootDir, patterns) {
4966
+ const files = await fg4(patterns, {
4967
+ cwd: rootDir,
4968
+ onlyFiles: true,
4969
+ absolute: false
4970
+ });
4971
+ const agents = [];
4972
+ const seen = /* @__PURE__ */ new Map();
4973
+ for (const file of files) {
4974
+ const normalizedFile = file.replace(/\\/g, "/");
4975
+ const fileName = normalizedFile.split("/").pop();
4976
+ if (fileName !== "handler.ts" && fileName !== "handler.js") {
4977
+ continue;
4978
+ }
4979
+ const absPath = path17.resolve(rootDir, normalizedFile);
4980
+ const source = await fs18.promises.readFile(absPath, "utf8").catch(() => "");
4981
+ const { hasRun } = detectAgentExports(source);
4982
+ const name = extractAgentNameFromPath(normalizedFile);
4983
+ const prevFile = seen.get(name);
4984
+ if (prevFile) {
4985
+ throw new Error(
4986
+ `Agent conflict: "${name}" declared in both ${prevFile} and ${normalizedFile}`
4987
+ );
4988
+ }
4989
+ seen.set(name, normalizedFile);
4990
+ agents.push({
4991
+ name,
4992
+ filePath: normalizedFile,
4993
+ hasRun
4994
+ });
4995
+ }
4996
+ return agents;
4997
+ }
4998
+
3969
4999
  // src/cli/createDevApp.ts
3970
5000
  init_createProgram();
3971
5001
  async function createDevApp(options) {
@@ -3988,6 +5018,22 @@ async function createDevApp(options) {
3988
5018
  }
3989
5019
  ctx.updateRoutes(sorted, reScanned.wsRoutes);
3990
5020
  };
5021
+ devApp.reloadTools = async () => {
5022
+ setLoadTimestamp(Date.now());
5023
+ invalidateProgramCache();
5024
+ const tools = await scanTools(ctx.rootDir, TOOL_PATTERNS);
5025
+ await generateToolArtifacts(tools, ctx.rootDir, ctx.dist, {
5026
+ skipSchema: isDevOnDemandEnabled()
5027
+ });
5028
+ await loadAndHydrateTools(ctx.rootDir, ctx.dist);
5029
+ };
5030
+ devApp.reloadAgents = async () => {
5031
+ setLoadTimestamp(Date.now());
5032
+ invalidateProgramCache();
5033
+ const agents = await scanAgents(ctx.rootDir, DEFAULT_AGENT_PATTERNS);
5034
+ await generateAgentArtifacts(agents, ctx.rootDir, ctx.dist);
5035
+ await loadAndHydrateAgents(ctx.rootDir, ctx.dist);
5036
+ };
3991
5037
  return devApp;
3992
5038
  }
3993
5039
 
@@ -4004,6 +5050,7 @@ export {
4004
5050
  RouteNotFoundError,
4005
5051
  SchemaExtractionError,
4006
5052
  ValidationError,
5053
+ clearAgentHandleFactory,
4007
5054
  collectRouteSchemaSources,
4008
5055
  cors,
4009
5056
  createProdApp as createApp,
@@ -4011,13 +5058,27 @@ export {
4011
5058
  createProdApp,
4012
5059
  createProgram,
4013
5060
  extractTypeInfo,
5061
+ getAgent,
5062
+ getAgentEntry,
4014
5063
  getApp,
4015
5064
  getInputTypeForMethod,
5065
+ getSkill,
5066
+ getTool,
4016
5067
  helmet,
5068
+ hydrateSkillRegistry,
4017
5069
  invalidateProgramCache,
5070
+ listSkills,
5071
+ loadAgentModule,
4018
5072
  loadConfig,
4019
5073
  loadEnv,
5074
+ loadToolModule,
5075
+ loadToolSchema,
4020
5076
  logger,
4021
- resolveTypeNode
5077
+ registerAgentHandleFactory,
5078
+ removeSkill,
5079
+ resolveAgentTools,
5080
+ resolveSubAgents,
5081
+ resolveTypeNode,
5082
+ upsertSkill
4022
5083
  };
4023
5084
  //# sourceMappingURL=index.js.map