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