@faapi/faapi 2.0.1-canary.0 → 3.0.0

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