@faapi/faapi 6.22.0 → 6.24.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.
@@ -60,13 +60,13 @@ interface ToolPathMeta {
60
60
  /**
61
61
  * Agent 的 LLM 可见核心字段
62
62
  *
63
- * 描述"agent 是什么"——LLM 真正需要消费的字段,**不含**代码本体加载细节
64
- * (filePath / hasRun)。文件型 agent 与 DB-driven skill 都实现此接口。
63
+ * 描述"agent 是什么"——LLM 真正需要消费的字段,**不含**声明文件定位信息
64
+ * (filePath)。文件型 agent 与 DB-driven skill 都实现此接口。
65
65
  *
66
66
  * - 文件型 agent:由 [AgentMetadata](./extractAgentMetadata.md) 继承扩展,
67
- * 额外含 `filePath` / `hasRun`(代码本体加载用)
67
+ * 额外含 `filePath`(声明文件定位用)
68
68
  * - DB-driven skill:业务方 plugin 从 DB 字段映射到本接口即可,无需填占位值
69
- * (skill 无源文件,不走 `loadAgentModule`,自然不读 filePath / hasRun)
69
+ * (skill 无源文件,自然不读 filePath)
70
70
  *
71
71
  * `@faapi/agent` 子包的 `Agent` 类、`agentRegistry` 查询入口、`asTool` 包装
72
72
  * 都消费 `AgentCore`,实现"agent 与 skill 走同一运行时链路"。
@@ -78,6 +78,16 @@ interface AgentCore {
78
78
  description?: string;
79
79
  /** 系统提示词(config 块字面量提取);文件型 agent 必填(构建期校验),DB skill 由业务方自治 */
80
80
  systemPrompt?: string;
81
+ /**
82
+ * 系统提示词文件路径(config 块字面量提取),相对产物 resources 目录
83
+ * (`<dist>/resources/`,即 `ctx.resourcesDir`),与 `systemPrompt` 互斥
84
+ * (都声明构建期抛错,二选一必填)
85
+ *
86
+ * 运行时每次 run 读文件内容作为 systemPrompt——dev 改 prompt 文件经 watcher
87
+ * 增量复制后立即生效,无需 reload;`deps.resourcesDir` 未注入或文件读取失败
88
+ * 时抛 `AgentError`(不静默降级)
89
+ */
90
+ systemPromptFile?: string;
81
91
  /** agent 显式声明可用的 tool 引用列表(config 块字面量提取),未声明时为 `undefined`;声明了但含无法静态求值的元素在构建期抛错 */
82
92
  tools?: string[];
83
93
  /** 可调用的其他 agent 名列表(config 块字面量提取),未声明时为 `undefined`;声明了但含无法静态求值的元素在构建期抛错 */
@@ -99,12 +109,13 @@ interface AgentCore {
99
109
  /**
100
110
  * Agent 完整元数据(文件型 agent)
101
111
  *
102
- * 继承 [AgentCore](./extractAgentMetadata.md) 的 LLM 字段,额外扩展**代码本体加载细节**:
103
- * - `filePath` — `loadAgentModule` 加载 `handler.js` 产物提取 `run` 函数用
104
- * - `hasRun` — 是否导出 `run` 函数(`Agent.executeSubAgent` 据此决定走自定义 run
105
- * 还是默认 reactLoop)
112
+ * 继承 [AgentCore](./extractAgentMetadata.md) 的 LLM 字段,额外扩展声明文件定位:
113
+ * - `filePath` — 声明文件源码相对路径(启动日志 / 清单可观测性用)
106
114
  *
107
- * DB-driven skill 不实现此接口(无源文件,无需加载),只实现 `AgentCore`。
115
+ * agent 统一为声明式执行(config + 默认 reactLoop)——自定义 `run` 导出已移除,
116
+ * 检测到 run 导出在提取阶段抛迁移错误(编排场景注册 tool)。
117
+ *
118
+ * DB-driven skill 不实现此接口(无源文件),只实现 `AgentCore`。
108
119
  *
109
120
  * 由 [extractAgentMetadata](./extractAgentMetadata.md) 产出,合并路径推导字段
110
121
  * (来自 [scanAgents](../agents/scanAgents.md) 的 `AgentManifest`)与 AST 提取字段
@@ -112,15 +123,13 @@ interface AgentCore {
112
123
  *
113
124
  * 字段来源:
114
125
  * - `name` — `@agent` JSDoc 覆盖值,或 `pathMeta.name`(目录推导)
115
- * - `filePath` / `hasRun` — 由 `pathMeta` 透传
126
+ * - `filePath` — 由 `pathMeta` 透传
116
127
  * - `description` — JSDoc 注释块自由文本(对 LLM 可见)
117
- * - `systemPrompt` / `tools` / `agents` / `model` / `maxTurns` / `inputDescription` — config 块字面量提取
128
+ * - `systemPrompt` / `systemPromptFile` / `tools` / `agents` / `model` / `maxTurns` / `inputDescription` — config 块字面量提取
118
129
  */
119
130
  interface AgentMetadata extends AgentCore {
120
- /** 源码相对路径(从 `pathMeta` 透传),`loadAgentModule` 据此加载 `handler.js` 提取 `run` */
131
+ /** 源码相对路径(从 `pathMeta` 透传)——声明文件定位与清单可观测性用 */
121
132
  filePath: string;
122
- /** 是否导出 `run` 函数(从 `pathMeta` 透传),`Agent.executeSubAgent` 据此选择自定义 run / 默认 reactLoop */
123
- hasRun: boolean;
124
133
  }
125
134
  /**
126
135
  * 路径推导的 agent 元数据(由 [scanAgents](../agents/scanAgents.ts) 计算)
@@ -133,8 +142,6 @@ interface AgentPathMeta {
133
142
  name: string;
134
143
  /** 源码相对路径(如 `src/agents/researcher/handler.ts`) */
135
144
  filePath: string;
136
- /** 是否导出 `run` 函数(scanAgents 正则检测) */
137
- hasRun: boolean;
138
145
  }
139
146
 
140
147
  /**
@@ -356,6 +363,8 @@ interface TaskWorkerOptions {
356
363
  /** signal 由执行器构造(abort/terminate 时触发),宿主只传 config 与 job 信息 */
357
364
  taskCtx: {
358
365
  config: unknown;
366
+ /** 产物 resources 目录绝对路径(纯字符串可结构化克隆,worker 内挂到 taskCtx) */
367
+ resourcesDir?: string;
359
368
  job: {
360
369
  id: string;
361
370
  name: string;
@@ -439,7 +448,7 @@ interface AgentRegistry {
439
448
  hydrate(agents: AgentMetadata[]): void;
440
449
  /** LLM 可见元数据(AgentCore) */
441
450
  getAgent(name: string): AgentCore | undefined;
442
- /** 完整元数据(含 filePath / hasRun,供加载 handler.js 执行 run) */
451
+ /** 完整元数据(含 filePath,声明文件定位) */
443
452
  getAgentEntry(name: string): AgentMetadata | undefined;
444
453
  /** 所有已注册 agent 的 LLM 可见元数据(副本) */
445
454
  listAgents(): AgentCore[];
@@ -602,7 +611,7 @@ interface TaskJob {
602
611
  * `TaskRegistriesView` 生成快照,worker wrapper 内重建只读视图。
603
612
  */
604
613
  interface TaskRegistriesSnapshot {
605
- /** agent 完整元数据(含 filePath/hasRun,非仅 LLM 可见字段) */
614
+ /** agent 完整元数据(含 filePath,非仅 LLM 可见字段) */
606
615
  agents: AgentMetadata[];
607
616
  tools: ToolMetadata[];
608
617
  skills: AgentCore[];
@@ -615,6 +624,14 @@ interface TaskContext {
615
624
  signal: AbortSignal;
616
625
  /** faapi.config.ts 全量配置(含自定义业务配置) */
617
626
  config: unknown;
627
+ /**
628
+ * 产物 resources 目录绝对路径(`<rootDir>/<dist>/resources`)
629
+ *
630
+ * 任务读静态文件(prompt 模板等)用:`fs.readFile(path.join(taskCtx.resourcesDir, 'x.md'))`。
631
+ * 进程内与隔离执行两条路径均注入(隔离为纯字符串,可结构化克隆);
632
+ * 直接构造 TaskContext 的测试/自定义执行器可不传。
633
+ */
634
+ resourcesDir?: string;
618
635
  job: {
619
636
  id: string;
620
637
  name: string;
@@ -625,7 +642,7 @@ interface TaskContext {
625
642
  *
626
643
  * 进程内执行为活引用;隔离执行为派发时刻的快照视图(worker 内重建)——
627
644
  * 执行中途的 reload/DB skill 变更不影响当次执行。
628
- * 任务内组装 agent 用 `registries.agent.getAgentEntry(name)`(含 filePath/hasRun)。
645
+ * 任务内组装 agent 用 `registries.agent.getAgentEntry(name)`(含 filePath)。
629
646
  */
630
647
  registries: TaskRegistriesView;
631
648
  /**
@@ -714,6 +731,11 @@ interface TaskQueueDeps {
714
731
  rootDir: string;
715
732
  /** faapi.config.ts 全量配置,透传给 run 的 TaskContext.config */
716
733
  config?: unknown;
734
+ /**
735
+ * 产物 resources 目录绝对路径——注入两条执行路径的 TaskContext.resourcesDir;
736
+ * 缺省不注入(直接构造队列的测试/嵌入场景)
737
+ */
738
+ resourcesDir?: string;
717
739
  /**
718
740
  * app 注册表只读视图(`createTaskRegistriesView`)——注入两条执行路径的
719
741
  * TaskContext.registries;缺省为空视图(直接构造队列的测试/嵌入场景)
@@ -1025,6 +1047,14 @@ interface FaapiContext {
1025
1047
  cookies: Record<string, string>;
1026
1048
  /** 配置文件中的自定义业务配置(类型可通过 declare module '@faapi/faapi' 增强 FaapiContextConfig) */
1027
1049
  config: FaapiContextConfig;
1050
+ /**
1051
+ * 运行时资源根目录绝对路径(<rootDir>/<dist>/resources)
1052
+ *
1053
+ * `src/resources/` 经 dev/build 复制进产物后的位置,handler 读静态文件用:
1054
+ * `await fs.readFile(path.join(ctx.resourcesDir, 'prompts/foo.md'))`。
1055
+ * HTTP/WS 请求链路恒有值;testing 直调(createTestContext)未传时为 undefined。
1056
+ */
1057
+ resourcesDir?: string;
1028
1058
  /**
1029
1059
  * 设置响应状态码
1030
1060
  */
package/dist/testing.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as FaapiContext, F as FaapiMiddleware, I as InjectorMap, R as RouteManifest, W as WsRouteManifest, C as CorsOptions, H as HelmetOptions } from './routeTypes-CXWkRx4W.js';
1
+ import { a as FaapiContext, F as FaapiMiddleware, I as InjectorMap, R as RouteManifest, W as WsRouteManifest, C as CorsOptions, H as HelmetOptions } from './routeTypes-D1swYDVC.js';
2
2
  import { Server } from 'node:http';
3
3
  import { WebSocket } from 'ws';
4
4
 
@@ -25,6 +25,8 @@ declare function createTestContext(options: CreateTestContextOptions): FaapiCont
25
25
  interface CreateTestContextOptions {
26
26
  /** app 级注册表(测试 handler 声明 agent/agents 参数时注入用,可选) */
27
27
  registries?: FaapiContext['registries'];
28
+ /** 运行时资源根目录(测试 handler 读 ctx.resourcesDir 时传入,可选) */
29
+ resourcesDir?: string;
28
30
  /** 请求方法,默认 'GET' */
29
31
  method?: string;
30
32
  /** 请求路径,必填,如 '/api/user'(无需写 host) */
package/dist/testing.js CHANGED
@@ -312,14 +312,14 @@ var ValidationError = class extends FaapiError {
312
312
  issues;
313
313
  };
314
314
  var RouteNotFoundError = class extends FaapiError {
315
- constructor(path16) {
316
- super("ROUTE_NOT_FOUND", `Route not found: ${path16}`, 404);
315
+ constructor(path17) {
316
+ super("ROUTE_NOT_FOUND", `Route not found: ${path17}`, 404);
317
317
  this.name = "RouteNotFoundError";
318
318
  }
319
319
  };
320
320
  var MethodNotAllowedError = class extends FaapiError {
321
- constructor(method, path16, allowedMethods) {
322
- super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path16}`, 405);
321
+ constructor(method, path17, allowedMethods) {
322
+ super("METHOD_NOT_ALLOWED", `Method ${method} not allowed for ${path17}`, 405);
323
323
  this.allowedMethods = allowedMethods;
324
324
  this.name = "MethodNotAllowedError";
325
325
  }
@@ -584,10 +584,18 @@ function formatSetCookie(name, value, options) {
584
584
  if (options?.sameSite) cookie += `; SameSite=${options.sameSite}`;
585
585
  return cookie;
586
586
  }
587
- function createContext(request, params, config = {}, ip = "", registries) {
588
- return createContextFromUrl(request, new URL(request.url), params, config, ip, registries);
587
+ function createContext(request, params, config = {}, ip = "", registries, resourcesDir) {
588
+ return createContextFromUrl(
589
+ request,
590
+ new URL(request.url),
591
+ params,
592
+ config,
593
+ ip,
594
+ registries,
595
+ resourcesDir
596
+ );
589
597
  }
590
- function createContextFromUrl(request, url, params, config = {}, ip = "", registries) {
598
+ function createContextFromUrl(request, url, params, config = {}, ip = "", registries, resourcesDir) {
591
599
  const meta = { headers: {}, setCookies: [] };
592
600
  const parsedCookies = parseCookies(request.headers.get("cookie") ?? "");
593
601
  const cookiesObj = {};
@@ -615,6 +623,9 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
615
623
  ua: request.headers.get("user-agent") ?? "",
616
624
  cookies: cookiesObj,
617
625
  config,
626
+ // 运行时资源根目录(HTTP/WS 链路由 createServer/handleWsUpgrade 传入;
627
+ // testing 直调未传时为 undefined,见 contextTypes.resourcesDir 注释)
628
+ resourcesDir,
618
629
  meta,
619
630
  setStatus(status) {
620
631
  meta.status = status;
@@ -720,15 +731,16 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
720
731
  function createTestContext(options) {
721
732
  const {
722
733
  method = "GET",
723
- path: path16,
734
+ path: path17,
724
735
  query,
725
736
  headers,
726
737
  params = {},
727
738
  config = {},
728
739
  ip = "",
729
- registries
740
+ registries,
741
+ resourcesDir
730
742
  } = options;
731
- const url = new URL(`http://localhost${path16}`);
743
+ const url = new URL(`http://localhost${path17}`);
732
744
  if (query) {
733
745
  for (const [key, value] of Object.entries(query)) {
734
746
  if (Array.isArray(value)) {
@@ -744,7 +756,7 @@ function createTestContext(options) {
744
756
  method,
745
757
  headers
746
758
  });
747
- return createContext(request, params, config, ip, registries);
759
+ return createContext(request, params, config, ip, registries, resourcesDir);
748
760
  }
749
761
 
750
762
  // src/response/pendingMeta.ts
@@ -1327,9 +1339,9 @@ async function invokeHandler(handler, ctx, body, middlewares, injectors) {
1327
1339
  }
1328
1340
 
1329
1341
  // src/testServer.ts
1330
- import path15 from "path";
1342
+ import path16 from "path";
1331
1343
  import os from "os";
1332
- import fs12 from "fs/promises";
1344
+ import fs13 from "fs/promises";
1333
1345
 
1334
1346
  // src/router/scanRoutes.ts
1335
1347
  import fg from "fast-glob";
@@ -1341,9 +1353,9 @@ var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
1341
1353
  var HTTP_METHOD_SET = new Set(HTTP_METHODS);
1342
1354
 
1343
1355
  // src/utils/normalizePath.ts
1344
- function normalizePath(path16) {
1345
- if (!path16) return "";
1346
- let result = path16.replace(/\\/g, "/");
1356
+ function normalizePath(path17) {
1357
+ if (!path17) return "";
1358
+ let result = path17.replace(/\\/g, "/");
1347
1359
  result = result.replace(/\/+/g, "/");
1348
1360
  result = result.replace(/\/+$/, "");
1349
1361
  if (result && !result.startsWith("/")) {
@@ -3511,13 +3523,14 @@ function buildAliasPlugins(rootDir) {
3511
3523
  }
3512
3524
 
3513
3525
  // src/cli/compileSourceFiles.ts
3526
+ var SOURCE_IGNORE = ["**/*.test.ts", "**/*.e2e.test.ts", "**/*.d.ts", `${APP_DIR}/resources/**`];
3514
3527
  async function compileSourceFiles(options) {
3515
3528
  const { rootDir, dist, files, logLevel = "silent", production, atomicWrite } = options;
3516
3529
  const entryPoints = files ?? await fg2([`${APP_DIR}/**/*.ts`], {
3517
3530
  cwd: rootDir,
3518
3531
  onlyFiles: true,
3519
3532
  absolute: true,
3520
- ignore: ["**/*.test.ts", "**/*.e2e.test.ts", "**/*.d.ts"]
3533
+ ignore: SOURCE_IGNORE
3521
3534
  });
3522
3535
  if (entryPoints.length === 0) {
3523
3536
  return { compiledFiles: [] };
@@ -3821,9 +3834,9 @@ async function validateInput(schemaPath, method, inputType, input) {
3821
3834
  function mapZodIssues(error) {
3822
3835
  return error.issues.map((issue) => {
3823
3836
  const code = mapZodCode(issue);
3824
- const path16 = issue.path.map(String).join(".") || "";
3837
+ const path17 = issue.path.map(String).join(".") || "";
3825
3838
  return {
3826
- path: path16,
3839
+ path: path17,
3827
3840
  code,
3828
3841
  expected: issue.expected ?? mapExpectedFromMessage(issue.message),
3829
3842
  received: issue.received ?? mapReceivedFromMessage(issue.message),
@@ -3873,7 +3886,7 @@ import {
3873
3886
  import { createSecureServer as createHttp2SecureServer } from "http2";
3874
3887
  import { readFileSync } from "fs";
3875
3888
  import { Readable as Readable2 } from "stream";
3876
- import path14 from "path";
3889
+ import path15 from "path";
3877
3890
 
3878
3891
  // src/router/matchRoute.ts
3879
3892
  var httpIndexCache = /* @__PURE__ */ new WeakMap();
@@ -3915,25 +3928,25 @@ function getWsIndex(routes) {
3915
3928
  wsIndexCache.set(routes, index);
3916
3929
  return index;
3917
3930
  }
3918
- function matchRoute(routes, method, path16) {
3931
+ function matchRoute(routes, method, path17) {
3919
3932
  const index = getHttpIndex(routes);
3920
3933
  const upper = method.toUpperCase();
3921
- const hit = matchByMethod(index, upper, path16);
3934
+ const hit = matchByMethod(index, upper, path17);
3922
3935
  if (hit) return hit;
3923
3936
  if (upper === "HEAD") {
3924
- return matchByMethod(index, "GET", path16);
3937
+ return matchByMethod(index, "GET", path17);
3925
3938
  }
3926
3939
  return null;
3927
3940
  }
3928
- function matchByMethod(index, method, path16) {
3929
- const staticHit = index.static.get(`${method}|${path16}`);
3941
+ function matchByMethod(index, method, path17) {
3942
+ const staticHit = index.static.get(`${method}|${path17}`);
3930
3943
  if (staticHit) {
3931
3944
  return { route: staticHit, params: {} };
3932
3945
  }
3933
3946
  if (index.dynamics.length === 0) {
3934
3947
  return null;
3935
3948
  }
3936
- const pathSegments = path16.split("/").filter(Boolean);
3949
+ const pathSegments = path17.split("/").filter(Boolean);
3937
3950
  for (const entry of index.dynamics) {
3938
3951
  const route = entry.route;
3939
3952
  if (route.method !== method) {
@@ -3946,31 +3959,31 @@ function matchByMethod(index, method, path16) {
3946
3959
  }
3947
3960
  return null;
3948
3961
  }
3949
- function matchWsRoute(wsRoutes, path16) {
3962
+ function matchWsRoute(wsRoutes, path17) {
3950
3963
  const index = getWsIndex(wsRoutes);
3951
- const staticHit = index.static.get(path16);
3964
+ const staticHit = index.static.get(path17);
3952
3965
  if (staticHit) {
3953
3966
  return { route: staticHit, params: {} };
3954
3967
  }
3955
3968
  for (const route of index.dynamics) {
3956
- const params = matchDynamicPath(route.urlPath, path16, route.paramNames, route.isCatchAll);
3969
+ const params = matchDynamicPath(route.urlPath, path17, route.paramNames, route.isCatchAll);
3957
3970
  if (params !== null) {
3958
3971
  return { route, params };
3959
3972
  }
3960
3973
  }
3961
3974
  return null;
3962
3975
  }
3963
- function findAllowedMethods(routes, path16) {
3976
+ function findAllowedMethods(routes, path17) {
3964
3977
  const index = getHttpIndex(routes);
3965
3978
  const methods = /* @__PURE__ */ new Set();
3966
- const staticMethods = index.methodsByStaticPath.get(path16);
3979
+ const staticMethods = index.methodsByStaticPath.get(path17);
3967
3980
  if (staticMethods) {
3968
3981
  for (const method of staticMethods) {
3969
3982
  methods.add(method);
3970
3983
  }
3971
3984
  }
3972
3985
  if (index.dynamics.length > 0) {
3973
- const pathSegments = path16.split("/").filter(Boolean);
3986
+ const pathSegments = path17.split("/").filter(Boolean);
3974
3987
  for (const entry of index.dynamics) {
3975
3988
  const params = matchSegments(
3976
3989
  entry.segments,
@@ -3988,10 +4001,10 @@ function findAllowedMethods(routes, path16) {
3988
4001
  }
3989
4002
  return Array.from(methods);
3990
4003
  }
3991
- function matchDynamicPath(pattern, path16, paramNames, isCatchAll) {
4004
+ function matchDynamicPath(pattern, path17, paramNames, isCatchAll) {
3992
4005
  return matchSegments(
3993
4006
  pattern.split("/").filter(Boolean),
3994
- path16.split("/").filter(Boolean),
4007
+ path17.split("/").filter(Boolean),
3995
4008
  paramNames,
3996
4009
  isCatchAll
3997
4010
  );
@@ -4613,9 +4626,17 @@ function logger(options = {}) {
4613
4626
  }
4614
4627
 
4615
4628
  // src/server/handleWsUpgrade.ts
4616
- import fs11 from "fs";
4629
+ import fs12 from "fs";
4617
4630
  import { WebSocketServer, WebSocket } from "ws";
4631
+ import path14 from "path";
4632
+
4633
+ // src/cli/copyResources.ts
4618
4634
  import path13 from "path";
4635
+ import fs11 from "fs";
4636
+ var RESOURCES_OUTPUT_DIR = "resources";
4637
+ function resolveResourcesDir(rootDir, dist) {
4638
+ return path13.resolve(rootDir, dist, RESOURCES_OUTPUT_DIR);
4639
+ }
4619
4640
 
4620
4641
  // src/server/serverUtils.ts
4621
4642
  function nodeHttpToWebHeaders(req) {
@@ -4673,7 +4694,7 @@ async function loadWsHandler(filePath, ctx, rootDir) {
4673
4694
  const dist = getDevDist();
4674
4695
  if (dist) {
4675
4696
  const sourcePath = prodPathToSourcePath(filePath, rootDir, dist);
4676
- if (sourcePath && fs11.existsSync(sourcePath)) {
4697
+ if (sourcePath && fs12.existsSync(sourcePath)) {
4677
4698
  await ensureCompiled(sourcePath, rootDir, dist);
4678
4699
  }
4679
4700
  }
@@ -4771,6 +4792,7 @@ function attachWebSocket(options) {
4771
4792
  trustedProxy = false,
4772
4793
  registries
4773
4794
  } = options;
4795
+ const resourcesDir = resolveResourcesDir(rootDir, dist);
4774
4796
  const wss = new WebSocketServer({ noServer: true });
4775
4797
  server.on("upgrade", async (req, socket, head) => {
4776
4798
  try {
@@ -4802,12 +4824,19 @@ function attachWebSocket(options) {
4802
4824
  const host = req.headers.host ?? "localhost";
4803
4825
  const fullUrl = new URL(`http://${host}${req.url ?? "/"}`);
4804
4826
  const request = new Request(fullUrl.toString(), { method: "GET", headers });
4805
- const ctx = createContext(request, params, config, getClientIp(req, trustedProxy), registries);
4827
+ const ctx = createContext(
4828
+ request,
4829
+ params,
4830
+ config,
4831
+ getClientIp(req, trustedProxy),
4832
+ registries,
4833
+ resourcesDir
4834
+ );
4806
4835
  const meta = ctx.meta;
4807
4836
  let upgraded = false;
4808
4837
  const finalHandler = async () => {
4809
4838
  const schemaPath = getRuntimeSchemaPath(route.filePath, dist, rootDir);
4810
- if (fs11.existsSync(schemaPath)) {
4839
+ if (fs12.existsSync(schemaPath)) {
4811
4840
  if (isDevOnDemandEnabled()) {
4812
4841
  const devDist = getDevDist();
4813
4842
  if (devDist) {
@@ -4846,7 +4875,7 @@ function attachWebSocket(options) {
4846
4875
  }
4847
4876
  let handlers;
4848
4877
  try {
4849
- const absoluteFilePath = path13.resolve(rootDir, route.filePath);
4878
+ const absoluteFilePath = path14.resolve(rootDir, route.filePath);
4850
4879
  handlers = await loadWsHandler(absoluteFilePath, ctx, rootDir);
4851
4880
  } catch (err) {
4852
4881
  const reason = err instanceof Error ? err.message : String(err);
@@ -5005,6 +5034,7 @@ function createServer(options) {
5005
5034
  trustedProxy = false
5006
5035
  } = options;
5007
5036
  const routesRef = { current: routes, wsCurrent: wsRoutes ?? [] };
5037
+ const resourcesDir = resolveResourcesDir(rootDir, dist);
5008
5038
  const configMiddlewares = [];
5009
5039
  if (compressionOption) {
5010
5040
  const compOpts = typeof compressionOption === "object" ? compressionOption : {};
@@ -5051,7 +5081,8 @@ function createServer(options) {
5051
5081
  bodyLimit,
5052
5082
  trustedProxy,
5053
5083
  registries,
5054
- routesRef.wsCurrent
5084
+ routesRef.wsCurrent,
5085
+ resourcesDir
5055
5086
  ).catch((err) => {
5056
5087
  console.error("[faapi] Request pipeline failed after error response attempt:", err);
5057
5088
  try {
@@ -5077,7 +5108,7 @@ function createServer(options) {
5077
5108
  });
5078
5109
  return { server, routesRef };
5079
5110
  }
5080
- function prepareRequest(req, request, url, config, trustedProxy, registries, preMatched) {
5111
+ function prepareRequest(req, request, url, config, trustedProxy, registries, preMatched, resourcesDir) {
5081
5112
  const method = request.method.toUpperCase();
5082
5113
  const urlPath = url.pathname;
5083
5114
  const ctx = createContextFromUrl(
@@ -5086,7 +5117,8 @@ function prepareRequest(req, request, url, config, trustedProxy, registries, pre
5086
5117
  preMatched?.params ?? {},
5087
5118
  config,
5088
5119
  getClientIp(req, trustedProxy),
5089
- registries
5120
+ registries,
5121
+ resourcesDir
5090
5122
  );
5091
5123
  const meta = ctx.meta;
5092
5124
  return { request, url, ctx, meta, method, urlPath };
@@ -5105,7 +5137,7 @@ function getRoutePaths(route, rootDir, dist) {
5105
5137
  let cached = routePathCache.get(route);
5106
5138
  if (!cached) {
5107
5139
  cached = {
5108
- absFilePath: path14.resolve(rootDir, route.filePath),
5140
+ absFilePath: path15.resolve(rootDir, route.filePath),
5109
5141
  schemaPath: getRuntimeSchemaPath(route.filePath, dist, rootDir)
5110
5142
  };
5111
5143
  routePathCache.set(route, cached);
@@ -5228,7 +5260,7 @@ async function sendErrorResponse(err, meta, res, onError, ctx) {
5228
5260
  }
5229
5261
  }
5230
5262
  }
5231
- async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares, onError, config, globalInjectors, bodyLimit, trustedProxy, registries, wsRoutes) {
5263
+ async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares, onError, config, globalInjectors, bodyLimit, trustedProxy, registries, wsRoutes, resourcesDir) {
5232
5264
  let meta = { headers: {}, setCookies: [] };
5233
5265
  let ctx;
5234
5266
  let sseEarlySent = false;
@@ -5248,7 +5280,8 @@ async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares,
5248
5280
  config,
5249
5281
  trustedProxy,
5250
5282
  registries,
5251
- preMatched
5283
+ preMatched,
5284
+ resourcesDir
5252
5285
  );
5253
5286
  ctx = prepared.ctx;
5254
5287
  meta = prepared.meta;
@@ -5313,7 +5346,7 @@ async function createTestServer(options) {
5313
5346
  } = options;
5314
5347
  const { routes, wsRoutes } = await scanRoutes(rootDir, patterns);
5315
5348
  const sorted = sortRoutes(routes);
5316
- const schemaDist = dist ? path15.isAbsolute(dist) ? dist : path15.resolve(rootDir, dist) : await fs12.mkdtemp(path15.join(os.tmpdir(), "faapi-test-schema-"));
5349
+ const schemaDist = dist ? path16.isAbsolute(dist) ? dist : path16.resolve(rootDir, dist) : await fs13.mkdtemp(path16.join(os.tmpdir(), "faapi-test-schema-"));
5317
5350
  await generateSchemaFiles(sorted, rootDir, schemaDist);
5318
5351
  const { server } = createServer({
5319
5352
  routes: sorted,
@@ -5345,7 +5378,7 @@ async function createTestServer(options) {
5345
5378
  await new Promise((resolve) => {
5346
5379
  server.close(() => resolve());
5347
5380
  });
5348
- await fs12.rm(schemaDist, { recursive: true, force: true }).catch(() => {
5381
+ await fs13.rm(schemaDist, { recursive: true, force: true }).catch(() => {
5349
5382
  });
5350
5383
  invalidateSchemaCache();
5351
5384
  }