@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/cli/index.js CHANGED
@@ -579,37 +579,42 @@ function setCachedMiddlewares(absPath, bundle) {
579
579
  middlewareCache.set(absPath, bundle);
580
580
  }
581
581
  async function loadMiddlewaresFile(filePath) {
582
+ let module;
582
583
  try {
583
- const module = await importWithCacheBust(filePath);
584
- const middlewares = module.default ?? module.middlewares ?? [];
585
- if (!Array.isArray(middlewares)) {
586
- console.warn(`[faapi] middlewares.ts \u5E94\u5BFC\u51FA\u6570\u7EC4\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
587
- return { middlewares: [], injectors: {} };
584
+ module = await importWithCacheBust(filePath);
585
+ } catch (err) {
586
+ console.error(
587
+ `[faapi] Failed to load middlewares from ${filePath}:`,
588
+ err instanceof Error ? err.stack ?? err.message : err
589
+ );
590
+ return { middlewares: [], injectors: {} };
591
+ }
592
+ const middlewares = module.default ?? module.middlewares ?? [];
593
+ if (!Array.isArray(middlewares)) {
594
+ console.warn(`[faapi] middlewares.ts \u5E94\u5BFC\u51FA\u6570\u7EC4\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
595
+ return { middlewares: [], injectors: {} };
596
+ }
597
+ const validMiddlewares = middlewares.filter((m) => {
598
+ if (typeof m !== "function") {
599
+ console.warn(`[faapi] \u65E0\u6548\u7684\u4E2D\u95F4\u4EF6\u9879\uFF08\u5E94\u4E3A\u51FD\u6570\uFF09\uFF0C\u5DF2\u5FFD\u7565: ${typeof m}`);
600
+ return false;
588
601
  }
589
- const validMiddlewares = middlewares.filter((m) => {
590
- if (typeof m !== "function") {
591
- console.warn(`[faapi] \u65E0\u6548\u7684\u4E2D\u95F4\u4EF6\u9879\uFF08\u5E94\u4E3A\u51FD\u6570\uFF09\uFF0C\u5DF2\u5FFD\u7565: ${typeof m}`);
592
- return false;
593
- }
594
- return true;
595
- });
596
- const injectors = module.injectors ?? {};
597
- if (typeof injectors !== "object" || injectors === null) {
598
- console.warn(`[faapi] injectors \u5E94\u5BFC\u51FA\u5BF9\u8C61\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
599
- return { middlewares: validMiddlewares, injectors: {} };
600
- }
601
- const validInjectors = {};
602
- for (const [name, injector] of Object.entries(injectors)) {
603
- if (typeof injector !== "function") {
604
- console.warn(`[faapi] \u6CE8\u5165\u5668 ${name} \u5E94\u4E3A\u51FD\u6570\uFF0C\u5DF2\u5FFD\u7565`);
605
- continue;
606
- }
607
- validInjectors[name] = injector;
602
+ return true;
603
+ });
604
+ const injectors = module.injectors ?? {};
605
+ if (typeof injectors !== "object" || injectors === null) {
606
+ console.warn(`[faapi] injectors \u5E94\u5BFC\u51FA\u5BF9\u8C61\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
607
+ return { middlewares: validMiddlewares, injectors: {} };
608
+ }
609
+ const validInjectors = {};
610
+ for (const [name, injector] of Object.entries(injectors)) {
611
+ if (typeof injector !== "function") {
612
+ console.warn(`[faapi] \u6CE8\u5165\u5668 ${name} \u5E94\u4E3A\u51FD\u6570\uFF0C\u5DF2\u5FFD\u7565`);
613
+ continue;
608
614
  }
609
- return { middlewares: validMiddlewares, injectors: validInjectors };
610
- } catch {
611
- return { middlewares: [], injectors: {} };
615
+ validInjectors[name] = injector;
612
616
  }
617
+ return { middlewares: validMiddlewares, injectors: validInjectors };
613
618
  }
614
619
  async function loadMergedMiddlewares(middlewarePaths) {
615
620
  if (middlewarePaths.length === 0) return void 0;
@@ -2485,7 +2490,6 @@ function extractAgentNameFromPath(filePath) {
2485
2490
  }
2486
2491
  function detectAgentExports(source) {
2487
2492
  return {
2488
- hasConfig: CONFIG_EXPORT_RE.test(source),
2489
2493
  hasRun: RUN_EXPORT_RE.test(source)
2490
2494
  };
2491
2495
  }
@@ -2505,7 +2509,7 @@ async function scanAgents(rootDir, patterns) {
2505
2509
  }
2506
2510
  const absPath = path10.resolve(rootDir, normalizedFile);
2507
2511
  const source = await fs9.promises.readFile(absPath, "utf8").catch(() => "");
2508
- const { hasConfig, hasRun } = detectAgentExports(source);
2512
+ const { hasRun } = detectAgentExports(source);
2509
2513
  const name = extractAgentNameFromPath(normalizedFile);
2510
2514
  const prevFile = seen.get(name);
2511
2515
  if (prevFile) {
@@ -2517,18 +2521,16 @@ async function scanAgents(rootDir, patterns) {
2517
2521
  agents.push({
2518
2522
  name,
2519
2523
  filePath: normalizedFile,
2520
- hasConfig,
2521
2524
  hasRun
2522
2525
  });
2523
2526
  }
2524
2527
  return agents;
2525
2528
  }
2526
- var DEFAULT_AGENT_PATTERNS, CONFIG_EXPORT_RE, RUN_EXPORT_RE;
2529
+ var DEFAULT_AGENT_PATTERNS, RUN_EXPORT_RE;
2527
2530
  var init_scanAgents = __esm({
2528
2531
  "src/agents/scanAgents.ts"() {
2529
2532
  "use strict";
2530
2533
  DEFAULT_AGENT_PATTERNS = ["src/agents/*/handler.ts"];
2531
- CONFIG_EXPORT_RE = /export\s+(?:const|function)\s+config\b/;
2532
2534
  RUN_EXPORT_RE = /export\s+(?:async\s+)?(?:function\s+|const\s+)run\b/;
2533
2535
  }
2534
2536
  });
@@ -2570,7 +2572,6 @@ function extractAgentMetadata(program, filePath, pathMeta) {
2570
2572
  name: agentNameOverride ?? pathMeta.name,
2571
2573
  description,
2572
2574
  filePath: pathMeta.filePath,
2573
- hasConfig: pathMeta.hasConfig,
2574
2575
  hasRun: pathMeta.hasRun,
2575
2576
  systemPrompt,
2576
2577
  tools,
@@ -2741,7 +2742,6 @@ function serializeAgents(agents, dist = "dist") {
2741
2742
  return agents.map((a) => ({
2742
2743
  name: a.name,
2743
2744
  description: a.description,
2744
- hasConfig: a.hasConfig,
2745
2745
  hasRun: a.hasRun,
2746
2746
  systemPrompt: a.systemPrompt,
2747
2747
  tools: a.tools,
@@ -2764,7 +2764,6 @@ function hydrateAgents(manifest) {
2764
2764
  name: a.name,
2765
2765
  description: a.description ?? void 0,
2766
2766
  filePath: a.filePath,
2767
- hasConfig: a.hasConfig,
2768
2767
  hasRun: a.hasRun,
2769
2768
  systemPrompt: a.systemPrompt ?? void 0,
2770
2769
  tools: a.tools ?? void 0,
@@ -2781,7 +2780,6 @@ async function generateAgentArtifacts(agents, rootDir, dist) {
2781
2780
  const result = extractAgentMetadata(program, absPath, {
2782
2781
  name: manifest.name,
2783
2782
  filePath: manifest.filePath,
2784
- hasConfig: manifest.hasConfig,
2785
2783
  hasRun: manifest.hasRun
2786
2784
  });
2787
2785
  if (result) {
@@ -4921,11 +4919,28 @@ function isProductFresh(sourceAbsPath, productAbsPath) {
4921
4919
  return false;
4922
4920
  }
4923
4921
  }
4922
+ function createDevOnDemandState() {
4923
+ return {
4924
+ enabled: false,
4925
+ distDir: void 0,
4926
+ compiledFiles: /* @__PURE__ */ new Set(),
4927
+ generatedSchemas: /* @__PURE__ */ new Set(),
4928
+ inFlightCompilations: /* @__PURE__ */ new Map(),
4929
+ inFlightSchemaGenerations: /* @__PURE__ */ new Map()
4930
+ };
4931
+ }
4924
4932
  function clearCompiledFiles() {
4925
- compiledFiles.clear();
4933
+ state.compiledFiles.clear();
4934
+ state.inFlightCompilations.clear();
4926
4935
  }
4927
4936
  async function ensureCompiled(sourceAbsPath, rootDir, dist) {
4928
- if (compiledFiles.has(sourceAbsPath)) {
4937
+ const inFlight = state.inFlightCompilations.get(sourceAbsPath);
4938
+ if (inFlight) {
4939
+ await inFlight.catch(() => {
4940
+ });
4941
+ return false;
4942
+ }
4943
+ if (state.compiledFiles.has(sourceAbsPath)) {
4929
4944
  return false;
4930
4945
  }
4931
4946
  if (!fs14.existsSync(sourceAbsPath)) {
@@ -4933,17 +4948,25 @@ async function ensureCompiled(sourceAbsPath, rootDir, dist) {
4933
4948
  }
4934
4949
  const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);
4935
4950
  if (productPath && isProductFresh(sourceAbsPath, productPath)) {
4936
- compiledFiles.add(sourceAbsPath);
4951
+ state.compiledFiles.add(sourceAbsPath);
4937
4952
  return false;
4938
4953
  }
4939
- await compileDevRoutes({
4940
- rootDir,
4941
- dist,
4942
- files: [sourceAbsPath],
4943
- logLevel: "silent"
4944
- });
4945
- compiledFiles.add(sourceAbsPath);
4946
- return true;
4954
+ const compilePromise = (async () => {
4955
+ await compileDevRoutes({
4956
+ rootDir,
4957
+ dist,
4958
+ files: [sourceAbsPath],
4959
+ logLevel: "silent"
4960
+ });
4961
+ state.compiledFiles.add(sourceAbsPath);
4962
+ })();
4963
+ state.inFlightCompilations.set(sourceAbsPath, compilePromise);
4964
+ try {
4965
+ await compilePromise;
4966
+ return true;
4967
+ } finally {
4968
+ state.inFlightCompilations.delete(sourceAbsPath);
4969
+ }
4947
4970
  }
4948
4971
  function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
4949
4972
  const rel = path16.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
@@ -4953,10 +4976,17 @@ function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
4953
4976
  return path16.resolve(rootDir, dist, jsRel);
4954
4977
  }
4955
4978
  function clearGeneratedSchemas() {
4956
- generatedSchemas.clear();
4979
+ state.generatedSchemas.clear();
4980
+ state.inFlightSchemaGenerations.clear();
4957
4981
  }
4958
4982
  async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist) {
4959
- if (generatedSchemas.has(schemaPath)) {
4983
+ const inFlight = state.inFlightSchemaGenerations.get(schemaPath);
4984
+ if (inFlight) {
4985
+ await inFlight.catch(() => {
4986
+ });
4987
+ return false;
4988
+ }
4989
+ if (state.generatedSchemas.has(schemaPath)) {
4960
4990
  return false;
4961
4991
  }
4962
4992
  const prodAbsPath = path16.resolve(rootDir, routeFilePath);
@@ -4965,7 +4995,7 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
4965
4995
  return false;
4966
4996
  }
4967
4997
  if (isProductFresh(sourceAbsPath, schemaPath)) {
4968
- generatedSchemas.add(schemaPath);
4998
+ state.generatedSchemas.add(schemaPath);
4969
4999
  return false;
4970
5000
  }
4971
5001
  const fileRoutes = routes.filter((r) => r.filePath === routeFilePath);
@@ -4974,9 +5004,17 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
4974
5004
  }
4975
5005
  const sourceRelPath = path16.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
4976
5006
  const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
4977
- await generateSchemaFiles(sourceRoutes, rootDir, dist);
4978
- generatedSchemas.add(schemaPath);
4979
- return true;
5007
+ const generatePromise = (async () => {
5008
+ await generateSchemaFiles(sourceRoutes, rootDir, dist);
5009
+ state.generatedSchemas.add(schemaPath);
5010
+ })();
5011
+ state.inFlightSchemaGenerations.set(schemaPath, generatePromise);
5012
+ try {
5013
+ await generatePromise;
5014
+ return true;
5015
+ } finally {
5016
+ state.inFlightSchemaGenerations.delete(schemaPath);
5017
+ }
4980
5018
  }
4981
5019
  async function deleteSchemaFiles(routes, rootDir, dist) {
4982
5020
  const deleted = /* @__PURE__ */ new Set();
@@ -5003,27 +5041,25 @@ function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
5003
5041
  return path16.resolve(rootDir, srcRel);
5004
5042
  }
5005
5043
  function setDevOnDemandEnabled(enabled) {
5006
- devOnDemandEnabled = enabled;
5044
+ state.enabled = enabled;
5007
5045
  }
5008
5046
  function isDevOnDemandEnabled() {
5009
- return devOnDemandEnabled;
5047
+ return state.enabled;
5010
5048
  }
5011
5049
  function setDevDist(dist) {
5012
- devDistDir = dist;
5050
+ state.distDir = dist;
5013
5051
  }
5014
5052
  function getDevDist() {
5015
- return devDistDir;
5053
+ return state.distDir;
5016
5054
  }
5017
- var compiledFiles, generatedSchemas, devOnDemandEnabled, devDistDir;
5055
+ var state;
5018
5056
  var init_compileOnDemand = __esm({
5019
5057
  "src/cli/compileOnDemand.ts"() {
5020
5058
  "use strict";
5021
5059
  init_compileDevRoutes();
5022
5060
  init_generateSchemaFiles();
5023
5061
  init_generateSchemaFiles();
5024
- compiledFiles = /* @__PURE__ */ new Set();
5025
- generatedSchemas = /* @__PURE__ */ new Set();
5026
- devOnDemandEnabled = false;
5062
+ state = createDevOnDemandState();
5027
5063
  }
5028
5064
  });
5029
5065
 
@@ -5177,6 +5213,164 @@ var init_sse = __esm({
5177
5213
  }
5178
5214
  });
5179
5215
 
5216
+ // src/errors/FaapiError.ts
5217
+ var FaapiError;
5218
+ var init_FaapiError = __esm({
5219
+ "src/errors/FaapiError.ts"() {
5220
+ "use strict";
5221
+ FaapiError = class extends Error {
5222
+ constructor(code, message, statusCode) {
5223
+ super(message);
5224
+ this.code = code;
5225
+ this.statusCode = statusCode;
5226
+ this.name = "FaapiError";
5227
+ }
5228
+ code;
5229
+ statusCode;
5230
+ };
5231
+ }
5232
+ });
5233
+
5234
+ // src/errors/httpErrors.ts
5235
+ function deriveStatusCode(issues) {
5236
+ const has400 = issues.some((i) => i.code === "INVALID_FORMAT" || i.code === "MISSING_FIELD");
5237
+ return has400 ? 400 : 422;
5238
+ }
5239
+ var ValidationError, RouteNotFoundError, MethodNotAllowedError, InternalError, PayloadTooLargeError;
5240
+ var init_httpErrors = __esm({
5241
+ "src/errors/httpErrors.ts"() {
5242
+ "use strict";
5243
+ init_FaapiError();
5244
+ ValidationError = class extends FaapiError {
5245
+ constructor(message, issues) {
5246
+ super("VALIDATION_ERROR", message, deriveStatusCode(issues));
5247
+ this.issues = issues;
5248
+ this.name = "ValidationError";
5249
+ }
5250
+ issues;
5251
+ };
5252
+ RouteNotFoundError = class extends FaapiError {
5253
+ constructor(path23) {
5254
+ super("ROUTE_NOT_FOUND", `Route not found: ${path23}`, 404);
5255
+ this.name = "RouteNotFoundError";
5256
+ }
5257
+ };
5258
+ MethodNotAllowedError = class extends FaapiError {
5259
+ constructor(method, path23, allowedMethods) {
5260
+ super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path23}`, 405);
5261
+ this.allowedMethods = allowedMethods;
5262
+ this.name = "MethodNotAllowedError";
5263
+ }
5264
+ allowedMethods;
5265
+ };
5266
+ InternalError = class extends FaapiError {
5267
+ constructor(message) {
5268
+ super("INTERNAL_ERROR", message, 500);
5269
+ this.name = "InternalError";
5270
+ }
5271
+ };
5272
+ PayloadTooLargeError = class extends FaapiError {
5273
+ constructor(maxSize) {
5274
+ super("PAYLOAD_TOO_LARGE", `Request body exceeds size limit of ${maxSize} bytes`, 413);
5275
+ this.name = "PayloadTooLargeError";
5276
+ }
5277
+ };
5278
+ }
5279
+ });
5280
+
5281
+ // src/response/responseFormatter.ts
5282
+ function defaultOk(data) {
5283
+ return { data };
5284
+ }
5285
+ function defaultFail(e) {
5286
+ const error = { message: e.message };
5287
+ if (e.code !== void 0) error.code = e.code;
5288
+ return { error };
5289
+ }
5290
+ function getResponseConfig(config) {
5291
+ return config?.response;
5292
+ }
5293
+ function resolveOkFn(config) {
5294
+ return getResponseConfig(config)?.ok ?? defaultOk;
5295
+ }
5296
+ function resolveFailFn(config) {
5297
+ return getResponseConfig(config)?.fail ?? defaultFail;
5298
+ }
5299
+ function jsonOk(body, status = 200, extraHeaders) {
5300
+ return jsonRaw(body, status, extraHeaders);
5301
+ }
5302
+ function jsonRaw(body, status, extraHeaders) {
5303
+ const headers = new Headers({ "Content-Type": "application/json" });
5304
+ if (extraHeaders) {
5305
+ const extra = new Headers(extraHeaders);
5306
+ extra.forEach((value, key) => headers.set(key, value));
5307
+ }
5308
+ return new Response(JSON.stringify(body), { status, headers });
5309
+ }
5310
+ function wrapOkResult(result, config) {
5311
+ if (result instanceof Response) return result;
5312
+ return resolveOkFn(config)(result);
5313
+ }
5314
+ function formatFailResponse(options, config) {
5315
+ const failFn = resolveFailFn(config);
5316
+ const body = failFn({
5317
+ status: options.status,
5318
+ code: options.code,
5319
+ message: options.message
5320
+ });
5321
+ return jsonOk(body, options.status ?? 500);
5322
+ }
5323
+ function formatErrorResponse(error, config) {
5324
+ const failFn = resolveFailFn(config);
5325
+ if (error instanceof ValidationError) {
5326
+ const body2 = failFn({
5327
+ status: error.statusCode,
5328
+ code: error.code,
5329
+ message: error.message
5330
+ });
5331
+ const bodyObj = typeof body2 === "object" && body2 !== null ? body2 : { error: body2 };
5332
+ const errorObj = bodyObj.error ?? bodyObj;
5333
+ if (errorObj) {
5334
+ errorObj.issues = error.issues;
5335
+ }
5336
+ return jsonOk(bodyObj, error.statusCode);
5337
+ }
5338
+ if (error instanceof MethodNotAllowedError) {
5339
+ const body2 = failFn({
5340
+ status: error.statusCode,
5341
+ code: error.code,
5342
+ message: error.message
5343
+ });
5344
+ return jsonOk(body2, error.statusCode, { Allow: error.allowedMethods.join(", ") });
5345
+ }
5346
+ if (error instanceof PayloadTooLargeError) {
5347
+ const body2 = failFn({
5348
+ status: error.statusCode,
5349
+ code: error.code,
5350
+ message: error.message
5351
+ });
5352
+ return jsonOk(body2, error.statusCode);
5353
+ }
5354
+ if (error instanceof FaapiError) {
5355
+ const body2 = failFn({
5356
+ status: error.statusCode,
5357
+ code: error.code,
5358
+ message: error.message
5359
+ });
5360
+ return jsonOk(body2, error.statusCode);
5361
+ }
5362
+ const message = error instanceof Error ? error.message : "An unknown error occurred";
5363
+ const body = failFn({ status: 500, code: "INTERNAL_ERROR", message });
5364
+ return jsonOk(body, 500);
5365
+ }
5366
+ var init_responseFormatter = __esm({
5367
+ "src/response/responseFormatter.ts"() {
5368
+ "use strict";
5369
+ init_FaapiError();
5370
+ init_httpErrors();
5371
+ }
5372
+ });
5373
+
5180
5374
  // src/runtime/createContext.ts
5181
5375
  function parseCookies(cookieHeader) {
5182
5376
  const cookies = /* @__PURE__ */ new Map();
@@ -5237,11 +5431,7 @@ function createContext(request, params, config = {}, ip = "") {
5237
5431
  });
5238
5432
  },
5239
5433
  json(data, status) {
5240
- const headers = { "Content-Type": "application/json" };
5241
- return new Response(JSON.stringify(data), {
5242
- status: status ?? 200,
5243
- headers
5244
- });
5434
+ return jsonOk(data, status ?? 200);
5245
5435
  },
5246
5436
  html(html, status) {
5247
5437
  const headers = { "Content-Type": "text/html; charset=utf-8" };
@@ -5277,18 +5467,22 @@ function createContext(request, params, config = {}, ip = "") {
5277
5467
  /**
5278
5468
  * 显式包装成功响应(返回 Response,不会被自动包裹再次包装)
5279
5469
  *
5470
+ * 实现委托给 [responseFormatter.wrapOkResult](../response/responseFormatter.ts),
5471
+ * 与 handler `return data` 走的自动包裹路径共享同一套 ok 函数。
5472
+ *
5280
5473
  * 用 config.response.ok(或默认 (data) => ({ data })) 包裹 data 并返回 JSON Response。
5281
5474
  * handler 也可直接 return data,框架会自动用 ok 包裹,两者等价。
5282
5475
  */
5283
5476
  ok(data) {
5284
- const responseConfig = config.response;
5285
- const okFn = responseConfig?.ok ?? ((d) => ({ data: d }));
5286
- const body = okFn(data);
5287
- return ctx.json(body);
5477
+ const body = wrapOkResult(data, config);
5478
+ return jsonOk(body, 200);
5288
5479
  },
5289
5480
  /**
5290
5481
  * 返回错误响应(对象形式参数,status 和 code 均可省略)
5291
5482
  *
5483
+ * 实现委托给 [responseFormatter.formatFailResponse](../response/responseFormatter.ts),
5484
+ * 与 formatErrorResponse(handler 抛错兜底)共享同一套 fail 函数,确保错误格式一致。
5485
+ *
5292
5486
  * - status 省略时 HTTP 状态码默认 500
5293
5487
  * - code 省略时响应 body 里不含 code 字段(默认 fail 函数只放非 undefined 的字段)
5294
5488
  * - status 和 code 独立无关联
@@ -5296,18 +5490,7 @@ function createContext(request, params, config = {}, ip = "") {
5296
5490
  * body 用 config.response.fail(或默认实现)包装。
5297
5491
  */
5298
5492
  fail(options) {
5299
- const responseConfig = config.response;
5300
- const failFn = responseConfig?.fail ?? ((e) => {
5301
- const error = { message: e.message };
5302
- if (e.code !== void 0) error.code = e.code;
5303
- return { error };
5304
- });
5305
- const body = failFn({
5306
- status: options.status,
5307
- code: options.code,
5308
- message: options.message
5309
- });
5310
- return ctx.json(body, options.status ?? 500);
5493
+ return formatFailResponse(options, config);
5311
5494
  }
5312
5495
  };
5313
5496
  const extend = config?.extendContext;
@@ -5320,6 +5503,7 @@ var init_createContext = __esm({
5320
5503
  "src/runtime/createContext.ts"() {
5321
5504
  "use strict";
5322
5505
  init_sse();
5506
+ init_responseFormatter();
5323
5507
  }
5324
5508
  });
5325
5509
 
@@ -5387,65 +5571,6 @@ var init_parseMultipart = __esm({
5387
5571
  }
5388
5572
  });
5389
5573
 
5390
- // src/errors/FaapiError.ts
5391
- var FaapiError;
5392
- var init_FaapiError = __esm({
5393
- "src/errors/FaapiError.ts"() {
5394
- "use strict";
5395
- FaapiError = class extends Error {
5396
- constructor(code, message, statusCode) {
5397
- super(message);
5398
- this.code = code;
5399
- this.statusCode = statusCode;
5400
- this.name = "FaapiError";
5401
- }
5402
- code;
5403
- statusCode;
5404
- };
5405
- }
5406
- });
5407
-
5408
- // src/errors/httpErrors.ts
5409
- function deriveStatusCode(issues) {
5410
- const has400 = issues.some((i) => i.code === "INVALID_FORMAT" || i.code === "MISSING_FIELD");
5411
- return has400 ? 400 : 422;
5412
- }
5413
- var ValidationError, RouteNotFoundError, MethodNotAllowedError, InternalError;
5414
- var init_httpErrors = __esm({
5415
- "src/errors/httpErrors.ts"() {
5416
- "use strict";
5417
- init_FaapiError();
5418
- ValidationError = class extends FaapiError {
5419
- constructor(message, issues) {
5420
- super("VALIDATION_ERROR", message, deriveStatusCode(issues));
5421
- this.issues = issues;
5422
- this.name = "ValidationError";
5423
- }
5424
- issues;
5425
- };
5426
- RouteNotFoundError = class extends FaapiError {
5427
- constructor(path23) {
5428
- super("ROUTE_NOT_FOUND", `Route not found: ${path23}`, 404);
5429
- this.name = "RouteNotFoundError";
5430
- }
5431
- };
5432
- MethodNotAllowedError = class extends FaapiError {
5433
- constructor(method, path23, allowedMethods) {
5434
- super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path23}`, 405);
5435
- this.allowedMethods = allowedMethods;
5436
- this.name = "MethodNotAllowedError";
5437
- }
5438
- allowedMethods;
5439
- };
5440
- InternalError = class extends FaapiError {
5441
- constructor(message) {
5442
- super("INTERNAL_ERROR", message, 500);
5443
- this.name = "InternalError";
5444
- }
5445
- };
5446
- }
5447
- });
5448
-
5449
5574
  // src/runtime/resolveInput.ts
5450
5575
  async function resolveInput(method, request) {
5451
5576
  const inputType = getInputTypeForMethod(method);
@@ -5627,25 +5752,44 @@ var init_toolRegistry = __esm({
5627
5752
  }
5628
5753
  });
5629
5754
 
5755
+ // src/injection/skillRegistry.ts
5756
+ function clearSkillRegistry() {
5757
+ registry2 = /* @__PURE__ */ new Map();
5758
+ }
5759
+ function listSkills() {
5760
+ return Array.from(registry2.values());
5761
+ }
5762
+ var registry2;
5763
+ var init_skillRegistry = __esm({
5764
+ "src/injection/skillRegistry.ts"() {
5765
+ "use strict";
5766
+ registry2 = /* @__PURE__ */ new Map();
5767
+ }
5768
+ });
5769
+
5630
5770
  // src/injection/agentRegistry.ts
5631
5771
  function hydrateAgentRegistry(agents) {
5632
5772
  const next = /* @__PURE__ */ new Map();
5633
5773
  for (const agent of agents) {
5634
5774
  next.set(agent.name, agent);
5635
5775
  }
5636
- registry2 = next;
5776
+ registry3 = next;
5637
5777
  }
5638
5778
  function clearAgentRegistry() {
5639
- registry2 = /* @__PURE__ */ new Map();
5779
+ registry3 = /* @__PURE__ */ new Map();
5640
5780
  }
5641
5781
  function listAgents() {
5642
- return Array.from(registry2.values());
5782
+ const merged = /* @__PURE__ */ new Map();
5783
+ for (const agent of registry3.values()) merged.set(agent.name, agent);
5784
+ for (const skill of listSkills()) merged.set(skill.name, skill);
5785
+ return Array.from(merged.values());
5643
5786
  }
5644
- var registry2;
5787
+ var registry3;
5645
5788
  var init_agentRegistry = __esm({
5646
5789
  "src/injection/agentRegistry.ts"() {
5647
5790
  "use strict";
5648
- registry2 = /* @__PURE__ */ new Map();
5791
+ init_skillRegistry();
5792
+ registry3 = /* @__PURE__ */ new Map();
5649
5793
  }
5650
5794
  });
5651
5795
 
@@ -5738,10 +5882,7 @@ var init_injectParams = __esm({
5738
5882
 
5739
5883
  // src/runtime/invokeHandler.ts
5740
5884
  function wrapResult(result, ctx) {
5741
- if (result instanceof Response) return result;
5742
- const responseConfig = ctx.config.response;
5743
- const okFn = responseConfig?.ok ?? ((d) => ({ data: d }));
5744
- return okFn(result);
5885
+ return wrapOkResult(result, ctx.config);
5745
5886
  }
5746
5887
  function mergeMeta(response, meta) {
5747
5888
  const hasMeta = meta.status !== void 0 || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0;
@@ -5830,6 +5971,7 @@ var init_invokeHandler = __esm({
5830
5971
  "src/runtime/invokeHandler.ts"() {
5831
5972
  "use strict";
5832
5973
  init_toResponse();
5974
+ init_responseFormatter();
5833
5975
  init_injectParams();
5834
5976
  }
5835
5977
  });
@@ -6160,55 +6302,10 @@ var init_logger = __esm({
6160
6302
  });
6161
6303
 
6162
6304
  // src/errors/formatErrorResponse.ts
6163
- function formatErrorResponse(error) {
6164
- if (error instanceof ValidationError) {
6165
- const body2 = {
6166
- code: error.code,
6167
- message: error.message,
6168
- issues: error.issues
6169
- };
6170
- return new Response(JSON.stringify({ error: body2 }), {
6171
- status: error.statusCode,
6172
- headers: { "Content-Type": "application/json" }
6173
- });
6174
- }
6175
- if (error instanceof MethodNotAllowedError) {
6176
- const body2 = {
6177
- code: error.code,
6178
- message: error.message
6179
- };
6180
- return new Response(JSON.stringify({ error: body2 }), {
6181
- status: error.statusCode,
6182
- headers: {
6183
- "Content-Type": "application/json",
6184
- Allow: error.allowedMethods.join(", ")
6185
- }
6186
- });
6187
- }
6188
- if (error instanceof FaapiError) {
6189
- const body2 = {
6190
- code: error.code,
6191
- message: error.message
6192
- };
6193
- return new Response(JSON.stringify({ error: body2 }), {
6194
- status: error.statusCode,
6195
- headers: { "Content-Type": "application/json" }
6196
- });
6197
- }
6198
- const body = {
6199
- code: "INTERNAL_ERROR",
6200
- message: error instanceof Error ? error.message : "An unknown error occurred"
6201
- };
6202
- return new Response(JSON.stringify({ error: body }), {
6203
- status: 500,
6204
- headers: { "Content-Type": "application/json" }
6205
- });
6206
- }
6207
6305
  var init_formatErrorResponse = __esm({
6208
6306
  "src/errors/formatErrorResponse.ts"() {
6209
6307
  "use strict";
6210
- init_FaapiError();
6211
- init_httpErrors();
6308
+ init_responseFormatter();
6212
6309
  }
6213
6310
  });
6214
6311
 
@@ -6225,9 +6322,9 @@ function nodeHttpToWebHeaders(req) {
6225
6322
  }
6226
6323
  return headers;
6227
6324
  }
6228
- function buildErrorResponse(err) {
6325
+ function buildErrorResponse(err, config) {
6229
6326
  try {
6230
- return formatErrorResponse(err);
6327
+ return formatErrorResponse(err, config);
6231
6328
  } catch {
6232
6329
  return new Response(
6233
6330
  JSON.stringify({ error: { code: "INTERNAL_ERROR", message: "Internal Server Error" } }),
@@ -6456,25 +6553,51 @@ function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT) {
6456
6553
  }
6457
6554
  function limitStreamSize(stream, maxSize) {
6458
6555
  let totalSize = 0;
6459
- const reader = stream.getReader();
6460
- return new ReadableStream({
6461
- async pull(controller) {
6462
- const { done, value } = await reader.read();
6463
- if (done) {
6464
- controller.close();
6556
+ let reader;
6557
+ let errored = false;
6558
+ const releaseReader = () => {
6559
+ if (reader) {
6560
+ try {
6465
6561
  reader.releaseLock();
6466
- return;
6562
+ } catch {
6467
6563
  }
6468
- totalSize += value.byteLength;
6469
- if (totalSize > maxSize) {
6470
- controller.error(new Error(`\u8BF7\u6C42\u4F53\u8D85\u8FC7\u5927\u5C0F\u9650\u5236 ${maxSize} \u5B57\u8282`));
6471
- reader.releaseLock();
6472
- return;
6564
+ reader = void 0;
6565
+ }
6566
+ };
6567
+ const failStream = (controller, err) => {
6568
+ if (errored) return;
6569
+ errored = true;
6570
+ controller.error(err instanceof Error ? err : new Error(String(err)));
6571
+ releaseReader();
6572
+ };
6573
+ return new ReadableStream({
6574
+ async pull(controller) {
6575
+ if (!reader) reader = stream.getReader();
6576
+ try {
6577
+ const { done, value } = await reader.read();
6578
+ if (done) {
6579
+ controller.close();
6580
+ releaseReader();
6581
+ return;
6582
+ }
6583
+ totalSize += value.byteLength;
6584
+ if (totalSize > maxSize) {
6585
+ failStream(controller, new PayloadTooLargeError(maxSize));
6586
+ return;
6587
+ }
6588
+ controller.enqueue(value);
6589
+ } catch (err) {
6590
+ failStream(controller, err);
6473
6591
  }
6474
- controller.enqueue(value);
6475
6592
  },
6476
6593
  cancel(reason) {
6477
- reader.cancel(reason);
6594
+ if (reader) {
6595
+ try {
6596
+ reader.cancel(reason);
6597
+ } catch {
6598
+ }
6599
+ releaseReader();
6600
+ }
6478
6601
  }
6479
6602
  });
6480
6603
  }
@@ -6555,21 +6678,27 @@ function createServer(options) {
6555
6678
  }
6556
6679
  return { server, routesRef };
6557
6680
  }
6558
- async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares, onError, config, globalMiddlewares, globalInjectors, bodyLimit) {
6681
+ function prepareRequest(req, config, bodyLimit) {
6559
6682
  const request = toWebRequest(req, bodyLimit);
6560
6683
  const method = request.method.toUpperCase();
6561
6684
  const urlPath = new URL(request.url).pathname;
6562
6685
  const ctx = createContext(request, {}, config, getClientIp(req));
6563
6686
  const meta = ctx.meta;
6564
- const routePipeline = async () => {
6565
- const match = matchRoute(routes, method, urlPath);
6566
- if (!match) {
6567
- const allowedMethods = findAllowedMethods(routes, urlPath);
6568
- if (allowedMethods.length > 0) {
6569
- throw new MethodNotAllowedError(method, urlPath, allowedMethods);
6570
- }
6571
- throw new RouteNotFoundError(urlPath);
6572
- }
6687
+ return { request, ctx, meta, method, urlPath };
6688
+ }
6689
+ function resolveRouteOrThrow(routes, method, urlPath) {
6690
+ const match = matchRoute(routes, method, urlPath);
6691
+ if (match) return match;
6692
+ const allowedMethods = findAllowedMethods(routes, urlPath);
6693
+ if (allowedMethods.length > 0) {
6694
+ throw new MethodNotAllowedError(method, urlPath, allowedMethods);
6695
+ }
6696
+ throw new RouteNotFoundError(urlPath);
6697
+ }
6698
+ function createRoutePipeline(opts) {
6699
+ const { routes, method, urlPath, ctx, request, rootDir, dist, globalInjectors } = opts;
6700
+ return async () => {
6701
+ const match = resolveRouteOrThrow(routes, method, urlPath);
6573
6702
  ctx.params = match.params;
6574
6703
  const { route } = match;
6575
6704
  const absoluteFilePath = path18.resolve(rootDir, route.filePath);
@@ -6599,37 +6728,44 @@ async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares,
6599
6728
  }
6600
6729
  }
6601
6730
  const mergedInjectors = globalInjectors ? { ...globalInjectors, ...route.injectors } : route.injectors;
6602
- const response = await invokeHandler(
6603
- routeModule.handler,
6604
- ctx,
6605
- body,
6606
- route.middlewares,
6607
- mergedInjectors
6608
- );
6609
- return response;
6731
+ return await invokeHandler(routeModule.handler, ctx, body, route.middlewares, mergedInjectors);
6610
6732
  };
6611
- try {
6612
- let response;
6613
- const outerMiddlewares = [];
6614
- if (configMiddlewares.length > 0) outerMiddlewares.push(...configMiddlewares);
6615
- if (globalMiddlewares && globalMiddlewares.length > 0) {
6616
- outerMiddlewares.push(...globalMiddlewares);
6617
- }
6618
- if (outerMiddlewares.length > 0) {
6619
- response = await compose(outerMiddlewares, ctx, routePipeline);
6620
- } else {
6621
- response = await routePipeline();
6733
+ }
6734
+ async function sendSuccessResponse(response, res) {
6735
+ await sendNodeResponse(response, res);
6736
+ }
6737
+ async function sendErrorResponse(err, meta, res, onError, ctx) {
6738
+ await sendNodeResponse(mergeMeta(buildErrorResponse(err, ctx.config), meta), res);
6739
+ if (onError) {
6740
+ try {
6741
+ await onError(err, ctx);
6742
+ } catch {
6622
6743
  }
6623
- await sendNodeResponse(response, res);
6744
+ }
6745
+ }
6746
+ async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares, onError, config, globalMiddlewares, globalInjectors, bodyLimit) {
6747
+ const { request, ctx, meta, method, urlPath } = prepareRequest(req, config, bodyLimit);
6748
+ const routePipeline = createRoutePipeline({
6749
+ routes,
6750
+ method,
6751
+ urlPath,
6752
+ ctx,
6753
+ request,
6754
+ rootDir,
6755
+ dist,
6756
+ globalMiddlewares,
6757
+ globalInjectors
6758
+ });
6759
+ const outerMiddlewares = [];
6760
+ if (configMiddlewares.length > 0) outerMiddlewares.push(...configMiddlewares);
6761
+ if (globalMiddlewares && globalMiddlewares.length > 0) {
6762
+ outerMiddlewares.push(...globalMiddlewares);
6763
+ }
6764
+ try {
6765
+ const response = outerMiddlewares.length > 0 ? await compose(outerMiddlewares, ctx, routePipeline) : await routePipeline();
6766
+ await sendSuccessResponse(response, res);
6624
6767
  } catch (err) {
6625
- const errorResponse = buildErrorResponse(err);
6626
- await sendNodeResponse(mergeMeta(errorResponse, meta), res);
6627
- if (onError) {
6628
- try {
6629
- await onError(err, ctx);
6630
- } catch {
6631
- }
6632
- }
6768
+ await sendErrorResponse(err, meta, res, onError, ctx);
6633
6769
  }
6634
6770
  }
6635
6771
  var DEFAULT_BODY_LIMIT;
@@ -6875,10 +7011,8 @@ async function createAppBase(options) {
6875
7011
  if (agents.length > 0) {
6876
7012
  console.log(`- Loaded ${agents.length} agent(s):`);
6877
7013
  for (const agent of agents) {
6878
- const exports = [];
6879
- if (agent.hasConfig) exports.push("config");
6880
- if (agent.hasRun) exports.push("run");
6881
- console.log(` ${agent.name} [${exports.join("+")}] ${agent.filePath}`);
7014
+ const exports = agent.hasRun ? "run" : "-";
7015
+ console.log(` ${agent.name} [${exports}] ${agent.filePath}`);
6882
7016
  }
6883
7017
  }
6884
7018
  if (config?.lifecycle?.onClose) {
@@ -6980,6 +7114,7 @@ async function createAppBase(options) {
6980
7114
  }
6981
7115
  clearToolRegistry();
6982
7116
  clearAgentRegistry();
7117
+ clearSkillRegistry();
6983
7118
  clearAgentHandleFactory();
6984
7119
  if (!server.listening) {
6985
7120
  app.server = null;
@@ -7031,6 +7166,7 @@ var init_createAppCore = __esm({
7031
7166
  init_importWithCacheBust();
7032
7167
  init_toolRegistry();
7033
7168
  init_agentRegistry();
7169
+ init_skillRegistry();
7034
7170
  init_agentHandle();
7035
7171
  DEFAULT_DIST = "dist";
7036
7172
  DEFAULT_PORT = 3e3;