@faapi/faapi 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1350,26 +1350,57 @@ function getTool(name) {
1350
1350
  return registry.get(name);
1351
1351
  }
1352
1352
 
1353
- // src/injection/agentRegistry.ts
1353
+ // src/injection/skillRegistry.ts
1354
1354
  var registry2 = /* @__PURE__ */ new Map();
1355
+ function hydrateSkillRegistry(skills) {
1356
+ const next = /* @__PURE__ */ new Map();
1357
+ for (const skill of skills) {
1358
+ next.set(skill.name, skill);
1359
+ }
1360
+ registry2 = next;
1361
+ }
1362
+ function clearSkillRegistry() {
1363
+ registry2 = /* @__PURE__ */ new Map();
1364
+ }
1365
+ function upsertSkill(core) {
1366
+ registry2.set(core.name, core);
1367
+ }
1368
+ function removeSkill(name) {
1369
+ registry2.delete(name);
1370
+ }
1371
+ function getSkill(name) {
1372
+ return registry2.get(name);
1373
+ }
1374
+ function listSkills() {
1375
+ return Array.from(registry2.values());
1376
+ }
1377
+
1378
+ // src/injection/agentRegistry.ts
1379
+ var registry3 = /* @__PURE__ */ new Map();
1355
1380
  function hydrateAgentRegistry(agents) {
1356
1381
  const next = /* @__PURE__ */ new Map();
1357
1382
  for (const agent of agents) {
1358
1383
  next.set(agent.name, agent);
1359
1384
  }
1360
- registry2 = next;
1385
+ registry3 = next;
1361
1386
  }
1362
1387
  function clearAgentRegistry() {
1363
- registry2 = /* @__PURE__ */ new Map();
1388
+ registry3 = /* @__PURE__ */ new Map();
1364
1389
  }
1365
1390
  function getAgent(name) {
1366
- return registry2.get(name);
1391
+ return getSkill(name) ?? registry3.get(name);
1392
+ }
1393
+ function getAgentEntry(name) {
1394
+ return registry3.get(name);
1367
1395
  }
1368
1396
  function listAgents() {
1369
- return Array.from(registry2.values());
1397
+ const merged = /* @__PURE__ */ new Map();
1398
+ for (const agent of registry3.values()) merged.set(agent.name, agent);
1399
+ for (const skill of listSkills()) merged.set(skill.name, skill);
1400
+ return Array.from(merged.values());
1370
1401
  }
1371
1402
  function resolveAgentTools(name) {
1372
- const agent = registry2.get(name);
1403
+ const agent = getAgent(name);
1373
1404
  if (!agent) return [];
1374
1405
  const result = /* @__PURE__ */ new Map();
1375
1406
  if (agent.tools) {
@@ -1381,11 +1412,11 @@ function resolveAgentTools(name) {
1381
1412
  return Array.from(result.values());
1382
1413
  }
1383
1414
  function resolveSubAgents(name) {
1384
- const agent = registry2.get(name);
1415
+ const agent = getAgent(name);
1385
1416
  if (!agent || !agent.agents) return [];
1386
1417
  const result = [];
1387
1418
  for (const agentName of agent.agents) {
1388
- const subAgent = registry2.get(agentName);
1419
+ const subAgent = getAgent(agentName);
1389
1420
  if (subAgent) result.push(subAgent);
1390
1421
  }
1391
1422
  return result;
@@ -1692,12 +1723,29 @@ function isProductFresh(sourceAbsPath, productAbsPath) {
1692
1723
  return false;
1693
1724
  }
1694
1725
  }
1695
- var compiledFiles = /* @__PURE__ */ new Set();
1726
+ function createDevOnDemandState() {
1727
+ return {
1728
+ enabled: false,
1729
+ distDir: void 0,
1730
+ compiledFiles: /* @__PURE__ */ new Set(),
1731
+ generatedSchemas: /* @__PURE__ */ new Set(),
1732
+ inFlightCompilations: /* @__PURE__ */ new Map(),
1733
+ inFlightSchemaGenerations: /* @__PURE__ */ new Map()
1734
+ };
1735
+ }
1736
+ var state = createDevOnDemandState();
1696
1737
  function clearCompiledFiles() {
1697
- compiledFiles.clear();
1738
+ state.compiledFiles.clear();
1739
+ state.inFlightCompilations.clear();
1698
1740
  }
1699
1741
  async function ensureCompiled(sourceAbsPath, rootDir, dist) {
1700
- if (compiledFiles.has(sourceAbsPath)) {
1742
+ const inFlight = state.inFlightCompilations.get(sourceAbsPath);
1743
+ if (inFlight) {
1744
+ await inFlight.catch(() => {
1745
+ });
1746
+ return false;
1747
+ }
1748
+ if (state.compiledFiles.has(sourceAbsPath)) {
1701
1749
  return false;
1702
1750
  }
1703
1751
  if (!fs5.existsSync(sourceAbsPath)) {
@@ -1705,17 +1753,25 @@ async function ensureCompiled(sourceAbsPath, rootDir, dist) {
1705
1753
  }
1706
1754
  const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);
1707
1755
  if (productPath && isProductFresh(sourceAbsPath, productPath)) {
1708
- compiledFiles.add(sourceAbsPath);
1756
+ state.compiledFiles.add(sourceAbsPath);
1709
1757
  return false;
1710
1758
  }
1711
- await compileDevRoutes({
1712
- rootDir,
1713
- dist,
1714
- files: [sourceAbsPath],
1715
- logLevel: "silent"
1716
- });
1717
- compiledFiles.add(sourceAbsPath);
1718
- return true;
1759
+ const compilePromise = (async () => {
1760
+ await compileDevRoutes({
1761
+ rootDir,
1762
+ dist,
1763
+ files: [sourceAbsPath],
1764
+ logLevel: "silent"
1765
+ });
1766
+ state.compiledFiles.add(sourceAbsPath);
1767
+ })();
1768
+ state.inFlightCompilations.set(sourceAbsPath, compilePromise);
1769
+ try {
1770
+ await compilePromise;
1771
+ return true;
1772
+ } finally {
1773
+ state.inFlightCompilations.delete(sourceAbsPath);
1774
+ }
1719
1775
  }
1720
1776
  function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
1721
1777
  const rel = path6.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
@@ -1724,12 +1780,18 @@ function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
1724
1780
  const jsRel = relWithoutSrc.replace(/\.ts$/, ".js");
1725
1781
  return path6.resolve(rootDir, dist, jsRel);
1726
1782
  }
1727
- var generatedSchemas = /* @__PURE__ */ new Set();
1728
1783
  function clearGeneratedSchemas() {
1729
- generatedSchemas.clear();
1784
+ state.generatedSchemas.clear();
1785
+ state.inFlightSchemaGenerations.clear();
1730
1786
  }
1731
1787
  async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist) {
1732
- if (generatedSchemas.has(schemaPath)) {
1788
+ const inFlight = state.inFlightSchemaGenerations.get(schemaPath);
1789
+ if (inFlight) {
1790
+ await inFlight.catch(() => {
1791
+ });
1792
+ return false;
1793
+ }
1794
+ if (state.generatedSchemas.has(schemaPath)) {
1733
1795
  return false;
1734
1796
  }
1735
1797
  const prodAbsPath = path6.resolve(rootDir, routeFilePath);
@@ -1738,7 +1800,7 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
1738
1800
  return false;
1739
1801
  }
1740
1802
  if (isProductFresh(sourceAbsPath, schemaPath)) {
1741
- generatedSchemas.add(schemaPath);
1803
+ state.generatedSchemas.add(schemaPath);
1742
1804
  return false;
1743
1805
  }
1744
1806
  const fileRoutes = routes.filter((r) => r.filePath === routeFilePath);
@@ -1747,9 +1809,17 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
1747
1809
  }
1748
1810
  const sourceRelPath = path6.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
1749
1811
  const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
1750
- await generateSchemaFiles(sourceRoutes, rootDir, dist);
1751
- generatedSchemas.add(schemaPath);
1752
- return true;
1812
+ const generatePromise = (async () => {
1813
+ await generateSchemaFiles(sourceRoutes, rootDir, dist);
1814
+ state.generatedSchemas.add(schemaPath);
1815
+ })();
1816
+ state.inFlightSchemaGenerations.set(schemaPath, generatePromise);
1817
+ try {
1818
+ await generatePromise;
1819
+ return true;
1820
+ } finally {
1821
+ state.inFlightSchemaGenerations.delete(schemaPath);
1822
+ }
1753
1823
  }
1754
1824
  async function deleteSchemaFiles(routes, rootDir, dist) {
1755
1825
  const deleted = /* @__PURE__ */ new Set();
@@ -1775,17 +1845,15 @@ function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
1775
1845
  if (fs5.existsSync(tsAbs)) return tsAbs;
1776
1846
  return path6.resolve(rootDir, srcRel);
1777
1847
  }
1778
- var devOnDemandEnabled = false;
1779
1848
  function isDevOnDemandEnabled() {
1780
- return devOnDemandEnabled;
1849
+ return state.enabled;
1781
1850
  }
1782
- var devDistDir;
1783
1851
  function getDevDist() {
1784
- return devDistDir;
1852
+ return state.distDir;
1785
1853
  }
1786
1854
 
1787
1855
  // src/loader/loadAgentModule.ts
1788
- async function loadAgentModule(filePath, hasConfig, hasRun, rootDir) {
1856
+ async function loadAgentModule(filePath, hasRun, rootDir) {
1789
1857
  if (isDevOnDemandEnabled() && rootDir) {
1790
1858
  const dist = getDevDist();
1791
1859
  if (dist) {
@@ -1809,30 +1877,6 @@ async function loadAgentModule(filePath, hasConfig, hasRun, rootDir) {
1809
1877
  const reason = err instanceof Error ? err.message : String(err);
1810
1878
  throw new Error(`Failed to load agent module "${filePath}": ${reason}`, { cause: err });
1811
1879
  }
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
- );
1819
- }
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
- );
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
- );
1834
- }
1835
- }
1836
1880
  let run;
1837
1881
  if (hasRun) {
1838
1882
  const runExport = resolveExport(module, "run");
@@ -1843,7 +1887,7 @@ async function loadAgentModule(filePath, hasConfig, hasRun, rootDir) {
1843
1887
  }
1844
1888
  run = runExport;
1845
1889
  }
1846
- return { config, run };
1890
+ return { run };
1847
1891
  }
1848
1892
 
1849
1893
  // src/loader/loadToolModule.ts
@@ -2509,6 +2553,12 @@ var ModuleLoadError = class extends FaapiError {
2509
2553
  this.name = "ModuleLoadError";
2510
2554
  }
2511
2555
  };
2556
+ var PayloadTooLargeError = class extends FaapiError {
2557
+ constructor(maxSize) {
2558
+ super("PAYLOAD_TOO_LARGE", `Request body exceeds size limit of ${maxSize} bytes`, 413);
2559
+ this.name = "PayloadTooLargeError";
2560
+ }
2561
+ };
2512
2562
 
2513
2563
  // src/cli/createAppCore.ts
2514
2564
  import fs15 from "fs";
@@ -2795,6 +2845,92 @@ function createSseWriter() {
2795
2845
  return writer;
2796
2846
  }
2797
2847
 
2848
+ // src/response/responseFormatter.ts
2849
+ function defaultOk(data) {
2850
+ return { data };
2851
+ }
2852
+ function defaultFail(e) {
2853
+ const error = { message: e.message };
2854
+ if (e.code !== void 0) error.code = e.code;
2855
+ return { error };
2856
+ }
2857
+ function getResponseConfig(config) {
2858
+ return config?.response;
2859
+ }
2860
+ function resolveOkFn(config) {
2861
+ return getResponseConfig(config)?.ok ?? defaultOk;
2862
+ }
2863
+ function resolveFailFn(config) {
2864
+ return getResponseConfig(config)?.fail ?? defaultFail;
2865
+ }
2866
+ function jsonOk(body, status = 200, extraHeaders) {
2867
+ return jsonRaw(body, status, extraHeaders);
2868
+ }
2869
+ function jsonRaw(body, status, extraHeaders) {
2870
+ const headers = new Headers({ "Content-Type": "application/json" });
2871
+ if (extraHeaders) {
2872
+ const extra = new Headers(extraHeaders);
2873
+ extra.forEach((value, key) => headers.set(key, value));
2874
+ }
2875
+ return new Response(JSON.stringify(body), { status, headers });
2876
+ }
2877
+ function wrapOkResult(result, config) {
2878
+ if (result instanceof Response) return result;
2879
+ return resolveOkFn(config)(result);
2880
+ }
2881
+ function formatFailResponse(options, config) {
2882
+ const failFn = resolveFailFn(config);
2883
+ const body = failFn({
2884
+ status: options.status,
2885
+ code: options.code,
2886
+ message: options.message
2887
+ });
2888
+ return jsonOk(body, options.status ?? 500);
2889
+ }
2890
+ function formatErrorResponse(error, config) {
2891
+ const failFn = resolveFailFn(config);
2892
+ if (error instanceof ValidationError) {
2893
+ const body2 = failFn({
2894
+ status: error.statusCode,
2895
+ code: error.code,
2896
+ message: error.message
2897
+ });
2898
+ const bodyObj = typeof body2 === "object" && body2 !== null ? body2 : { error: body2 };
2899
+ const errorObj = bodyObj.error ?? bodyObj;
2900
+ if (errorObj) {
2901
+ errorObj.issues = error.issues;
2902
+ }
2903
+ return jsonOk(bodyObj, error.statusCode);
2904
+ }
2905
+ if (error instanceof MethodNotAllowedError) {
2906
+ const body2 = failFn({
2907
+ status: error.statusCode,
2908
+ code: error.code,
2909
+ message: error.message
2910
+ });
2911
+ return jsonOk(body2, error.statusCode, { Allow: error.allowedMethods.join(", ") });
2912
+ }
2913
+ if (error instanceof PayloadTooLargeError) {
2914
+ const body2 = failFn({
2915
+ status: error.statusCode,
2916
+ code: error.code,
2917
+ message: error.message
2918
+ });
2919
+ return jsonOk(body2, error.statusCode);
2920
+ }
2921
+ if (error instanceof FaapiError) {
2922
+ const body2 = failFn({
2923
+ status: error.statusCode,
2924
+ code: error.code,
2925
+ message: error.message
2926
+ });
2927
+ return jsonOk(body2, error.statusCode);
2928
+ }
2929
+ const message = error instanceof Error ? error.message : "An unknown error occurred";
2930
+ const body = failFn({ status: 500, code: "INTERNAL_ERROR", message });
2931
+ return jsonOk(body, 500);
2932
+ }
2933
+
2798
2934
  // src/runtime/createContext.ts
2799
2935
  function parseCookies(cookieHeader) {
2800
2936
  const cookies = /* @__PURE__ */ new Map();
@@ -2855,11 +2991,7 @@ function createContext(request, params, config = {}, ip = "") {
2855
2991
  });
2856
2992
  },
2857
2993
  json(data, status) {
2858
- const headers = { "Content-Type": "application/json" };
2859
- return new Response(JSON.stringify(data), {
2860
- status: status ?? 200,
2861
- headers
2862
- });
2994
+ return jsonOk(data, status ?? 200);
2863
2995
  },
2864
2996
  html(html, status) {
2865
2997
  const headers = { "Content-Type": "text/html; charset=utf-8" };
@@ -2895,18 +3027,22 @@ function createContext(request, params, config = {}, ip = "") {
2895
3027
  /**
2896
3028
  * 显式包装成功响应(返回 Response,不会被自动包裹再次包装)
2897
3029
  *
3030
+ * 实现委托给 [responseFormatter.wrapOkResult](../response/responseFormatter.ts),
3031
+ * 与 handler `return data` 走的自动包裹路径共享同一套 ok 函数。
3032
+ *
2898
3033
  * 用 config.response.ok(或默认 (data) => ({ data })) 包裹 data 并返回 JSON Response。
2899
3034
  * handler 也可直接 return data,框架会自动用 ok 包裹,两者等价。
2900
3035
  */
2901
3036
  ok(data) {
2902
- const responseConfig = config.response;
2903
- const okFn = responseConfig?.ok ?? ((d) => ({ data: d }));
2904
- const body = okFn(data);
2905
- return ctx.json(body);
3037
+ const body = wrapOkResult(data, config);
3038
+ return jsonOk(body, 200);
2906
3039
  },
2907
3040
  /**
2908
3041
  * 返回错误响应(对象形式参数,status 和 code 均可省略)
2909
3042
  *
3043
+ * 实现委托给 [responseFormatter.formatFailResponse](../response/responseFormatter.ts),
3044
+ * 与 formatErrorResponse(handler 抛错兜底)共享同一套 fail 函数,确保错误格式一致。
3045
+ *
2910
3046
  * - status 省略时 HTTP 状态码默认 500
2911
3047
  * - code 省略时响应 body 里不含 code 字段(默认 fail 函数只放非 undefined 的字段)
2912
3048
  * - status 和 code 独立无关联
@@ -2914,18 +3050,7 @@ function createContext(request, params, config = {}, ip = "") {
2914
3050
  * body 用 config.response.fail(或默认实现)包装。
2915
3051
  */
2916
3052
  fail(options) {
2917
- const responseConfig = config.response;
2918
- const failFn = responseConfig?.fail ?? ((e) => {
2919
- const error = { message: e.message };
2920
- if (e.code !== void 0) error.code = e.code;
2921
- return { error };
2922
- });
2923
- const body = failFn({
2924
- status: options.status,
2925
- code: options.code,
2926
- message: options.message
2927
- });
2928
- return ctx.json(body, options.status ?? 500);
3053
+ return formatFailResponse(options, config);
2929
3054
  }
2930
3055
  };
2931
3056
  const extend = config?.extendContext;
@@ -3191,10 +3316,7 @@ async function injectParamsAsync(handler, ctx, body, injectors) {
3191
3316
 
3192
3317
  // src/runtime/invokeHandler.ts
3193
3318
  function wrapResult(result, ctx) {
3194
- if (result instanceof Response) return result;
3195
- const responseConfig = ctx.config.response;
3196
- const okFn = responseConfig?.ok ?? ((d) => ({ data: d }));
3197
- return okFn(result);
3319
+ return wrapOkResult(result, ctx.config);
3198
3320
  }
3199
3321
  function mergeMeta(response, meta) {
3200
3322
  const hasMeta = meta.status !== void 0 || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0;
@@ -3417,52 +3539,6 @@ import fs12 from "fs";
3417
3539
  import { WebSocketServer, WebSocket } from "ws";
3418
3540
  import path10 from "path";
3419
3541
 
3420
- // src/errors/formatErrorResponse.ts
3421
- function formatErrorResponse(error) {
3422
- if (error instanceof ValidationError) {
3423
- const body2 = {
3424
- code: error.code,
3425
- message: error.message,
3426
- issues: error.issues
3427
- };
3428
- return new Response(JSON.stringify({ error: body2 }), {
3429
- status: error.statusCode,
3430
- headers: { "Content-Type": "application/json" }
3431
- });
3432
- }
3433
- if (error instanceof MethodNotAllowedError) {
3434
- const body2 = {
3435
- code: error.code,
3436
- message: error.message
3437
- };
3438
- return new Response(JSON.stringify({ error: body2 }), {
3439
- status: error.statusCode,
3440
- headers: {
3441
- "Content-Type": "application/json",
3442
- Allow: error.allowedMethods.join(", ")
3443
- }
3444
- });
3445
- }
3446
- if (error instanceof FaapiError) {
3447
- const body2 = {
3448
- code: error.code,
3449
- message: error.message
3450
- };
3451
- return new Response(JSON.stringify({ error: body2 }), {
3452
- status: error.statusCode,
3453
- headers: { "Content-Type": "application/json" }
3454
- });
3455
- }
3456
- const body = {
3457
- code: "INTERNAL_ERROR",
3458
- message: error instanceof Error ? error.message : "An unknown error occurred"
3459
- };
3460
- return new Response(JSON.stringify({ error: body }), {
3461
- status: 500,
3462
- headers: { "Content-Type": "application/json" }
3463
- });
3464
- }
3465
-
3466
3542
  // src/server/serverUtils.ts
3467
3543
  function nodeHttpToWebHeaders(req) {
3468
3544
  const headers = new Headers();
@@ -3476,9 +3552,9 @@ function nodeHttpToWebHeaders(req) {
3476
3552
  }
3477
3553
  return headers;
3478
3554
  }
3479
- function buildErrorResponse(err) {
3555
+ function buildErrorResponse(err, config) {
3480
3556
  try {
3481
- return formatErrorResponse(err);
3557
+ return formatErrorResponse(err, config);
3482
3558
  } catch {
3483
3559
  return new Response(
3484
3560
  JSON.stringify({ error: { code: "INTERNAL_ERROR", message: "Internal Server Error" } }),
@@ -3502,37 +3578,42 @@ function setCachedMiddlewares(absPath, bundle) {
3502
3578
  middlewareCache.set(absPath, bundle);
3503
3579
  }
3504
3580
  async function loadMiddlewaresFile(filePath) {
3581
+ let module;
3505
3582
  try {
3506
- const module = await importWithCacheBust(filePath);
3507
- const middlewares = module.default ?? module.middlewares ?? [];
3508
- if (!Array.isArray(middlewares)) {
3509
- console.warn(`[faapi] middlewares.ts \u5E94\u5BFC\u51FA\u6570\u7EC4\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
3510
- return { middlewares: [], injectors: {} };
3511
- }
3512
- const validMiddlewares = middlewares.filter((m) => {
3513
- if (typeof m !== "function") {
3514
- console.warn(`[faapi] \u65E0\u6548\u7684\u4E2D\u95F4\u4EF6\u9879\uFF08\u5E94\u4E3A\u51FD\u6570\uFF09\uFF0C\u5DF2\u5FFD\u7565: ${typeof m}`);
3515
- return false;
3516
- }
3517
- return true;
3518
- });
3519
- const injectors = module.injectors ?? {};
3520
- if (typeof injectors !== "object" || injectors === null) {
3521
- console.warn(`[faapi] injectors \u5E94\u5BFC\u51FA\u5BF9\u8C61\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
3522
- return { middlewares: validMiddlewares, injectors: {} };
3523
- }
3524
- const validInjectors = {};
3525
- for (const [name, injector] of Object.entries(injectors)) {
3526
- if (typeof injector !== "function") {
3527
- console.warn(`[faapi] \u6CE8\u5165\u5668 ${name} \u5E94\u4E3A\u51FD\u6570\uFF0C\u5DF2\u5FFD\u7565`);
3528
- continue;
3529
- }
3530
- validInjectors[name] = injector;
3531
- }
3532
- return { middlewares: validMiddlewares, injectors: validInjectors };
3533
- } catch {
3583
+ module = await importWithCacheBust(filePath);
3584
+ } catch (err) {
3585
+ console.error(
3586
+ `[faapi] Failed to load middlewares from ${filePath}:`,
3587
+ err instanceof Error ? err.stack ?? err.message : err
3588
+ );
3589
+ return { middlewares: [], injectors: {} };
3590
+ }
3591
+ const middlewares = module.default ?? module.middlewares ?? [];
3592
+ if (!Array.isArray(middlewares)) {
3593
+ console.warn(`[faapi] middlewares.ts \u5E94\u5BFC\u51FA\u6570\u7EC4\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
3534
3594
  return { middlewares: [], injectors: {} };
3535
3595
  }
3596
+ const validMiddlewares = middlewares.filter((m) => {
3597
+ if (typeof m !== "function") {
3598
+ console.warn(`[faapi] \u65E0\u6548\u7684\u4E2D\u95F4\u4EF6\u9879\uFF08\u5E94\u4E3A\u51FD\u6570\uFF09\uFF0C\u5DF2\u5FFD\u7565: ${typeof m}`);
3599
+ return false;
3600
+ }
3601
+ return true;
3602
+ });
3603
+ const injectors = module.injectors ?? {};
3604
+ if (typeof injectors !== "object" || injectors === null) {
3605
+ console.warn(`[faapi] injectors \u5E94\u5BFC\u51FA\u5BF9\u8C61\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
3606
+ return { middlewares: validMiddlewares, injectors: {} };
3607
+ }
3608
+ const validInjectors = {};
3609
+ for (const [name, injector] of Object.entries(injectors)) {
3610
+ if (typeof injector !== "function") {
3611
+ console.warn(`[faapi] \u6CE8\u5165\u5668 ${name} \u5E94\u4E3A\u51FD\u6570\uFF0C\u5DF2\u5FFD\u7565`);
3612
+ continue;
3613
+ }
3614
+ validInjectors[name] = injector;
3615
+ }
3616
+ return { middlewares: validMiddlewares, injectors: validInjectors };
3536
3617
  }
3537
3618
  async function loadMergedMiddlewares(middlewarePaths) {
3538
3619
  if (middlewarePaths.length === 0) return void 0;
@@ -3739,25 +3820,51 @@ function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT) {
3739
3820
  }
3740
3821
  function limitStreamSize(stream, maxSize) {
3741
3822
  let totalSize = 0;
3742
- const reader = stream.getReader();
3743
- return new ReadableStream({
3744
- async pull(controller) {
3745
- const { done, value } = await reader.read();
3746
- if (done) {
3747
- controller.close();
3823
+ let reader;
3824
+ let errored = false;
3825
+ const releaseReader = () => {
3826
+ if (reader) {
3827
+ try {
3748
3828
  reader.releaseLock();
3749
- return;
3829
+ } catch {
3750
3830
  }
3751
- totalSize += value.byteLength;
3752
- if (totalSize > maxSize) {
3753
- controller.error(new Error(`\u8BF7\u6C42\u4F53\u8D85\u8FC7\u5927\u5C0F\u9650\u5236 ${maxSize} \u5B57\u8282`));
3754
- reader.releaseLock();
3755
- return;
3831
+ reader = void 0;
3832
+ }
3833
+ };
3834
+ const failStream = (controller, err) => {
3835
+ if (errored) return;
3836
+ errored = true;
3837
+ controller.error(err instanceof Error ? err : new Error(String(err)));
3838
+ releaseReader();
3839
+ };
3840
+ return new ReadableStream({
3841
+ async pull(controller) {
3842
+ if (!reader) reader = stream.getReader();
3843
+ try {
3844
+ const { done, value } = await reader.read();
3845
+ if (done) {
3846
+ controller.close();
3847
+ releaseReader();
3848
+ return;
3849
+ }
3850
+ totalSize += value.byteLength;
3851
+ if (totalSize > maxSize) {
3852
+ failStream(controller, new PayloadTooLargeError(maxSize));
3853
+ return;
3854
+ }
3855
+ controller.enqueue(value);
3856
+ } catch (err) {
3857
+ failStream(controller, err);
3756
3858
  }
3757
- controller.enqueue(value);
3758
3859
  },
3759
3860
  cancel(reason) {
3760
- reader.cancel(reason);
3861
+ if (reader) {
3862
+ try {
3863
+ reader.cancel(reason);
3864
+ } catch {
3865
+ }
3866
+ releaseReader();
3867
+ }
3761
3868
  }
3762
3869
  });
3763
3870
  }
@@ -3838,21 +3945,27 @@ function createServer(options) {
3838
3945
  }
3839
3946
  return { server, routesRef };
3840
3947
  }
3841
- async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares, onError, config, globalMiddlewares, globalInjectors, bodyLimit) {
3948
+ function prepareRequest(req, config, bodyLimit) {
3842
3949
  const request = toWebRequest(req, bodyLimit);
3843
3950
  const method = request.method.toUpperCase();
3844
3951
  const urlPath = new URL(request.url).pathname;
3845
3952
  const ctx = createContext(request, {}, config, getClientIp(req));
3846
3953
  const meta = ctx.meta;
3847
- const routePipeline = async () => {
3848
- const match = matchRoute(routes, method, urlPath);
3849
- if (!match) {
3850
- const allowedMethods = findAllowedMethods(routes, urlPath);
3851
- if (allowedMethods.length > 0) {
3852
- throw new MethodNotAllowedError(method, urlPath, allowedMethods);
3853
- }
3854
- throw new RouteNotFoundError(urlPath);
3855
- }
3954
+ return { request, ctx, meta, method, urlPath };
3955
+ }
3956
+ function resolveRouteOrThrow(routes, method, urlPath) {
3957
+ const match = matchRoute(routes, method, urlPath);
3958
+ if (match) return match;
3959
+ const allowedMethods = findAllowedMethods(routes, urlPath);
3960
+ if (allowedMethods.length > 0) {
3961
+ throw new MethodNotAllowedError(method, urlPath, allowedMethods);
3962
+ }
3963
+ throw new RouteNotFoundError(urlPath);
3964
+ }
3965
+ function createRoutePipeline(opts) {
3966
+ const { routes, method, urlPath, ctx, request, rootDir, dist, globalInjectors } = opts;
3967
+ return async () => {
3968
+ const match = resolveRouteOrThrow(routes, method, urlPath);
3856
3969
  ctx.params = match.params;
3857
3970
  const { route } = match;
3858
3971
  const absoluteFilePath = path11.resolve(rootDir, route.filePath);
@@ -3882,37 +3995,44 @@ async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares,
3882
3995
  }
3883
3996
  }
3884
3997
  const mergedInjectors = globalInjectors ? { ...globalInjectors, ...route.injectors } : route.injectors;
3885
- const response = await invokeHandler(
3886
- routeModule.handler,
3887
- ctx,
3888
- body,
3889
- route.middlewares,
3890
- mergedInjectors
3891
- );
3892
- return response;
3998
+ return await invokeHandler(routeModule.handler, ctx, body, route.middlewares, mergedInjectors);
3893
3999
  };
3894
- try {
3895
- let response;
3896
- const outerMiddlewares = [];
3897
- if (configMiddlewares.length > 0) outerMiddlewares.push(...configMiddlewares);
3898
- if (globalMiddlewares && globalMiddlewares.length > 0) {
3899
- outerMiddlewares.push(...globalMiddlewares);
3900
- }
3901
- if (outerMiddlewares.length > 0) {
3902
- response = await compose(outerMiddlewares, ctx, routePipeline);
3903
- } else {
3904
- response = await routePipeline();
4000
+ }
4001
+ async function sendSuccessResponse(response, res) {
4002
+ await sendNodeResponse(response, res);
4003
+ }
4004
+ async function sendErrorResponse(err, meta, res, onError, ctx) {
4005
+ await sendNodeResponse(mergeMeta(buildErrorResponse(err, ctx.config), meta), res);
4006
+ if (onError) {
4007
+ try {
4008
+ await onError(err, ctx);
4009
+ } catch {
3905
4010
  }
3906
- await sendNodeResponse(response, res);
4011
+ }
4012
+ }
4013
+ async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares, onError, config, globalMiddlewares, globalInjectors, bodyLimit) {
4014
+ const { request, ctx, meta, method, urlPath } = prepareRequest(req, config, bodyLimit);
4015
+ const routePipeline = createRoutePipeline({
4016
+ routes,
4017
+ method,
4018
+ urlPath,
4019
+ ctx,
4020
+ request,
4021
+ rootDir,
4022
+ dist,
4023
+ globalMiddlewares,
4024
+ globalInjectors
4025
+ });
4026
+ const outerMiddlewares = [];
4027
+ if (configMiddlewares.length > 0) outerMiddlewares.push(...configMiddlewares);
4028
+ if (globalMiddlewares && globalMiddlewares.length > 0) {
4029
+ outerMiddlewares.push(...globalMiddlewares);
4030
+ }
4031
+ try {
4032
+ const response = outerMiddlewares.length > 0 ? await compose(outerMiddlewares, ctx, routePipeline) : await routePipeline();
4033
+ await sendSuccessResponse(response, res);
3907
4034
  } catch (err) {
3908
- const errorResponse = buildErrorResponse(err);
3909
- await sendNodeResponse(mergeMeta(errorResponse, meta), res);
3910
- if (onError) {
3911
- try {
3912
- await onError(err, ctx);
3913
- } catch {
3914
- }
3915
- }
4035
+ await sendErrorResponse(err, meta, res, onError, ctx);
3916
4036
  }
3917
4037
  }
3918
4038
 
@@ -4011,7 +4131,6 @@ function extractAgentMetadata(program, filePath, pathMeta) {
4011
4131
  name: agentNameOverride ?? pathMeta.name,
4012
4132
  description,
4013
4133
  filePath: pathMeta.filePath,
4014
- hasConfig: pathMeta.hasConfig,
4015
4134
  hasRun: pathMeta.hasRun,
4016
4135
  systemPrompt,
4017
4136
  tools,
@@ -4177,7 +4296,6 @@ function serializeAgents(agents, dist = "dist") {
4177
4296
  return agents.map((a) => ({
4178
4297
  name: a.name,
4179
4298
  description: a.description,
4180
- hasConfig: a.hasConfig,
4181
4299
  hasRun: a.hasRun,
4182
4300
  systemPrompt: a.systemPrompt,
4183
4301
  tools: a.tools,
@@ -4200,7 +4318,6 @@ function hydrateAgents(manifest) {
4200
4318
  name: a.name,
4201
4319
  description: a.description ?? void 0,
4202
4320
  filePath: a.filePath,
4203
- hasConfig: a.hasConfig,
4204
4321
  hasRun: a.hasRun,
4205
4322
  systemPrompt: a.systemPrompt ?? void 0,
4206
4323
  tools: a.tools ?? void 0,
@@ -4217,7 +4334,6 @@ async function generateAgentArtifacts(agents, rootDir, dist) {
4217
4334
  const result = extractAgentMetadata(program, absPath, {
4218
4335
  name: manifest.name,
4219
4336
  filePath: manifest.filePath,
4220
- hasConfig: manifest.hasConfig,
4221
4337
  hasRun: manifest.hasRun
4222
4338
  });
4223
4339
  if (result) {
@@ -4435,10 +4551,8 @@ async function createAppBase(options) {
4435
4551
  if (agents.length > 0) {
4436
4552
  console.log(`- Loaded ${agents.length} agent(s):`);
4437
4553
  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}`);
4554
+ const exports = agent.hasRun ? "run" : "-";
4555
+ console.log(` ${agent.name} [${exports}] ${agent.filePath}`);
4442
4556
  }
4443
4557
  }
4444
4558
  if (config?.lifecycle?.onClose) {
@@ -4540,6 +4654,7 @@ async function createAppBase(options) {
4540
4654
  }
4541
4655
  clearToolRegistry();
4542
4656
  clearAgentRegistry();
4657
+ clearSkillRegistry();
4543
4658
  clearAgentHandleFactory();
4544
4659
  if (!server.listening) {
4545
4660
  app.server = null;
@@ -4831,7 +4946,6 @@ import fg4 from "fast-glob";
4831
4946
  import path17 from "path";
4832
4947
  import fs18 from "fs";
4833
4948
  var DEFAULT_AGENT_PATTERNS = ["src/agents/*/handler.ts"];
4834
- var CONFIG_EXPORT_RE = /export\s+(?:const|function)\s+config\b/;
4835
4949
  var RUN_EXPORT_RE = /export\s+(?:async\s+)?(?:function\s+|const\s+)run\b/;
4836
4950
  function extractAgentNameFromPath(filePath) {
4837
4951
  const normalized = filePath.replace(/\\/g, "/");
@@ -4845,7 +4959,6 @@ function extractAgentNameFromPath(filePath) {
4845
4959
  }
4846
4960
  function detectAgentExports(source) {
4847
4961
  return {
4848
- hasConfig: CONFIG_EXPORT_RE.test(source),
4849
4962
  hasRun: RUN_EXPORT_RE.test(source)
4850
4963
  };
4851
4964
  }
@@ -4865,7 +4978,7 @@ async function scanAgents(rootDir, patterns) {
4865
4978
  }
4866
4979
  const absPath = path17.resolve(rootDir, normalizedFile);
4867
4980
  const source = await fs18.promises.readFile(absPath, "utf8").catch(() => "");
4868
- const { hasConfig, hasRun } = detectAgentExports(source);
4981
+ const { hasRun } = detectAgentExports(source);
4869
4982
  const name = extractAgentNameFromPath(normalizedFile);
4870
4983
  const prevFile = seen.get(name);
4871
4984
  if (prevFile) {
@@ -4877,7 +4990,6 @@ async function scanAgents(rootDir, patterns) {
4877
4990
  agents.push({
4878
4991
  name,
4879
4992
  filePath: normalizedFile,
4880
- hasConfig,
4881
4993
  hasRun
4882
4994
  });
4883
4995
  }
@@ -4947,11 +5059,15 @@ export {
4947
5059
  createProgram,
4948
5060
  extractTypeInfo,
4949
5061
  getAgent,
5062
+ getAgentEntry,
4950
5063
  getApp,
4951
5064
  getInputTypeForMethod,
5065
+ getSkill,
4952
5066
  getTool,
4953
5067
  helmet,
5068
+ hydrateSkillRegistry,
4954
5069
  invalidateProgramCache,
5070
+ listSkills,
4955
5071
  loadAgentModule,
4956
5072
  loadConfig,
4957
5073
  loadEnv,
@@ -4959,8 +5075,10 @@ export {
4959
5075
  loadToolSchema,
4960
5076
  logger,
4961
5077
  registerAgentHandleFactory,
5078
+ removeSkill,
4962
5079
  resolveAgentTools,
4963
5080
  resolveSubAgents,
4964
- resolveTypeNode
5081
+ resolveTypeNode,
5082
+ upsertSkill
4965
5083
  };
4966
5084
  //# sourceMappingURL=index.js.map