@faapi/faapi 6.26.0 → 6.28.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
@@ -985,6 +985,25 @@ var init_sortRoutes = __esm({
985
985
  }
986
986
  });
987
987
 
988
+ // src/injection/subAgentToolName.ts
989
+ function subAgentToolName(agentName) {
990
+ const toolName = SUB_AGENT_TOOL_PREFIX + agentName;
991
+ if (!LLM_TOOL_NAME_PATTERN.test(toolName)) {
992
+ throw new Error(
993
+ `Sub-agent tool name "${toolName}" (from agent name "${agentName}") violates the LLM tool name pattern ${LLM_TOOL_NAME_PATTERN} \u2014 rename the agent (allowed: a-z A-Z 0-9 '-' '_'; file-based agent directory segments allow a-z A-Z 0-9 '-' only, '_' is the nesting separator)`
994
+ );
995
+ }
996
+ return toolName;
997
+ }
998
+ var LLM_TOOL_NAME_PATTERN, SUB_AGENT_TOOL_PREFIX;
999
+ var init_subAgentToolName = __esm({
1000
+ "src/injection/subAgentToolName.ts"() {
1001
+ "use strict";
1002
+ LLM_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
1003
+ SUB_AGENT_TOOL_PREFIX = "agent-";
1004
+ }
1005
+ });
1006
+
988
1007
  // src/tools/scanTools.ts
989
1008
  import fg2 from "fast-glob";
990
1009
  import path9 from "path";
@@ -1005,7 +1024,15 @@ function extractNamespaceFromRelPath(relPath) {
1005
1024
  const lastSlash = relPath.lastIndexOf("/");
1006
1025
  const dirPath = lastSlash === -1 ? "" : relPath.slice(0, lastSlash);
1007
1026
  if (!dirPath) return "";
1008
- return dirPath.split("/").join(".");
1027
+ const segments = dirPath.split("/");
1028
+ for (const segment of segments) {
1029
+ if (!TOOL_DIR_SEGMENT_PATTERN.test(segment)) {
1030
+ throw new Error(
1031
+ `Invalid tool directory segment "${segment}" in "tools/${relPath}": tool directory names allow a-z A-Z 0-9 '-' only ('_' is reserved as the nesting separator, '.' is not allowed \u2014 tool names are sent to LLMs as function.name) \u2014 rename the directory`
1032
+ );
1033
+ }
1034
+ }
1035
+ return segments.join("_");
1009
1036
  }
1010
1037
  function filePathToToolNamespace(filePath) {
1011
1038
  const normalized = filePath.replace(/\\/g, "/");
@@ -1016,7 +1043,13 @@ function filePathToToolNamespace(filePath) {
1016
1043
  return "";
1017
1044
  }
1018
1045
  function buildToolName(namespace, functionName) {
1019
- return namespace ? `${namespace}.${functionName}` : functionName;
1046
+ const name = namespace ? `${namespace}_${functionName}` : functionName;
1047
+ if (!LLM_TOOL_NAME_PATTERN.test(name)) {
1048
+ throw new Error(
1049
+ `Tool name "${name}" (function "${functionName}"${namespace ? ` in namespace "${namespace}"` : ""}) violates the LLM tool name pattern ${LLM_TOOL_NAME_PATTERN} \u2014 rename the function or use a @tool JSDoc override (allowed: a-z A-Z 0-9 '-' '_')`
1050
+ );
1051
+ }
1052
+ return name;
1020
1053
  }
1021
1054
  async function scanTools(rootDir, patterns) {
1022
1055
  const files = await fg2(patterns, {
@@ -1054,16 +1087,18 @@ async function scanTools(rootDir, patterns) {
1054
1087
  }
1055
1088
  return tools;
1056
1089
  }
1057
- var TOOL_PATTERNS, TOOL_EXPORT_RE, RESERVED_EXPORTS;
1090
+ var TOOL_PATTERNS, TOOL_EXPORT_RE, RESERVED_EXPORTS, TOOL_DIR_SEGMENT_PATTERN;
1058
1091
  var init_scanTools = __esm({
1059
1092
  "src/tools/scanTools.ts"() {
1060
1093
  "use strict";
1094
+ init_subAgentToolName();
1061
1095
  TOOL_PATTERNS = ["src/tools/**/*.ts"];
1062
1096
  TOOL_EXPORT_RE = new RegExp(
1063
1097
  String.raw`export\s+(?:async\s+)?(?:function\s+|const\s+)([A-Za-z_$][\w$]*)\s*(?:\(|=)`,
1064
1098
  "g"
1065
1099
  );
1066
1100
  RESERVED_EXPORTS = /* @__PURE__ */ new Set(["default", "config", "run"]);
1101
+ TOOL_DIR_SEGMENT_PATTERN = /^[a-zA-Z0-9-]+$/;
1067
1102
  }
1068
1103
  });
1069
1104
 
@@ -2898,6 +2933,14 @@ function extractToolMetadata(program, filePath, functionName, pathMeta) {
2898
2933
  const jsDoc = getJSDocFromNode(jsDocOwner);
2899
2934
  const description = extractDescription(jsDoc);
2900
2935
  const toolNameOverride = extractJSDocTagValue(jsDoc, "tool");
2936
+ if (toolNameOverride !== void 0 && !LLM_TOOL_NAME_PATTERN.test(toolNameOverride)) {
2937
+ throw SchemaExtractionError.at(
2938
+ jsDocOwner,
2939
+ "@tool",
2940
+ `\u8986\u76D6\u540D "${toolNameOverride}" \u542B\u975E\u6CD5\u5B57\u7B26\u2014\u2014\u987B\u6EE1\u8DB3 ${LLM_TOOL_NAME_PATTERN}\uFF08tool \u540D\u76F4\u63A5\u4F5C\u4E3A LLM function.name\uFF0C\u5F3A\u6821\u9A8C\u4E0A\u6E38\u5BF9\u975E\u6CD5\u5B57\u7B26\u6574\u5355 400\uFF09`,
2941
+ sourceFile
2942
+ );
2943
+ }
2901
2944
  const inputTypeName = getFirstParamTypeName(fn, sourceFile);
2902
2945
  return {
2903
2946
  name: toolNameOverride ?? pathMeta.name,
@@ -2939,6 +2982,8 @@ var init_extractToolMetadata = __esm({
2939
2982
  "src/ast/extractToolMetadata.ts"() {
2940
2983
  "use strict";
2941
2984
  init_jsDocMetadata();
2985
+ init_subAgentToolName();
2986
+ init_resolveTypeNode();
2942
2987
  }
2943
2988
  });
2944
2989
 
@@ -3090,7 +3135,15 @@ function extractAgentNameFromPath(filePath) {
3090
3135
  `Not an agent path: "${filePath}". Expected pattern: src/agents/<name>/handler.ts`
3091
3136
  );
3092
3137
  }
3093
- return match[1].replace(/\//g, ".");
3138
+ const segments = match[1].split("/");
3139
+ for (const segment of segments) {
3140
+ if (!AGENT_DIR_SEGMENT_PATTERN.test(segment)) {
3141
+ throw new Error(
3142
+ `Invalid agent directory segment "${segment}" in "${filePath}": agent directory names allow a-z A-Z 0-9 '-' only ('_' is reserved as the nesting separator, '.' is not allowed \u2014 agent names become LLM tool names "agent-<name>") \u2014 rename the directory`
3143
+ );
3144
+ }
3145
+ }
3146
+ return segments.join("_");
3094
3147
  }
3095
3148
  async function scanAgents(rootDir, patterns) {
3096
3149
  const files = await fg3(patterns, {
@@ -3121,11 +3174,12 @@ async function scanAgents(rootDir, patterns) {
3121
3174
  }
3122
3175
  return agents;
3123
3176
  }
3124
- var DEFAULT_AGENT_PATTERNS;
3177
+ var DEFAULT_AGENT_PATTERNS, AGENT_DIR_SEGMENT_PATTERN;
3125
3178
  var init_scanAgents = __esm({
3126
3179
  "src/agents/scanAgents.ts"() {
3127
3180
  "use strict";
3128
3181
  DEFAULT_AGENT_PATTERNS = ["src/agents/**/handler.ts"];
3182
+ AGENT_DIR_SEGMENT_PATTERN = /^[a-zA-Z0-9-]+$/;
3129
3183
  }
3130
3184
  });
3131
3185
 
@@ -3164,6 +3218,14 @@ function extractAgentMetadata(program, filePath, pathMeta) {
3164
3218
  const jsDoc = getJSDocFromNode(configFound.jsDocOwner);
3165
3219
  const description = extractDescription(jsDoc);
3166
3220
  const agentNameOverride = extractJSDocTagValue(jsDoc, "agent");
3221
+ if (agentNameOverride !== void 0 && !LLM_TOOL_NAME_PATTERN.test(agentNameOverride)) {
3222
+ throw SchemaExtractionError.at(
3223
+ configFound.jsDocOwner,
3224
+ "@agent",
3225
+ `\u8986\u76D6\u540D "${agentNameOverride}" \u542B\u975E\u6CD5\u5B57\u7B26\u2014\u2014\u987B\u6EE1\u8DB3 ${LLM_TOOL_NAME_PATTERN}\uFF08\u8986\u76D6\u540D\u6210\u4E3A LLM \u5DE5\u5177\u540D "agent-<\u8986\u76D6\u540D>"\uFF0C\u5F3A\u6821\u9A8C\u4E0A\u6E38\u5BF9\u975E\u6CD5\u5B57\u7B26\u6574\u5355 400\uFF09`,
3226
+ sourceFile
3227
+ );
3228
+ }
3167
3229
  const { systemPrompt, systemPromptFile, tools, agents, model, maxTurns, inputDescription } = extractConfigFields(objectLiteral, sourceFile);
3168
3230
  return {
3169
3231
  name: agentNameOverride ?? pathMeta.name,
@@ -3385,6 +3447,7 @@ var init_extractAgentMetadata = __esm({
3385
3447
  "use strict";
3386
3448
  init_jsDocMetadata();
3387
3449
  init_resolveTypeNode();
3450
+ init_subAgentToolName();
3388
3451
  }
3389
3452
  });
3390
3453
 
@@ -7080,18 +7143,10 @@ function formatSetCookie(name, value, options) {
7080
7143
  if (options?.sameSite) cookie += `; SameSite=${options.sameSite}`;
7081
7144
  return cookie;
7082
7145
  }
7083
- function createContext(request, params, config = {}, ip = "", registries, resourcesDir) {
7084
- return createContextFromUrl(
7085
- request,
7086
- new URL(request.url),
7087
- params,
7088
- config,
7089
- ip,
7090
- registries,
7091
- resourcesDir
7092
- );
7146
+ function createContext(request, params, config = {}, ip = "", registries) {
7147
+ return createContextFromUrl(request, new URL(request.url), params, config, ip, registries);
7093
7148
  }
7094
- function createContextFromUrl(request, url, params, config = {}, ip = "", registries, resourcesDir) {
7149
+ function createContextFromUrl(request, url, params, config = {}, ip = "", registries) {
7095
7150
  const meta = { headers: {}, setCookies: [] };
7096
7151
  const parsedCookies = parseCookies(request.headers.get("cookie") ?? "");
7097
7152
  const cookiesObj = {};
@@ -7119,9 +7174,6 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
7119
7174
  ua: request.headers.get("user-agent") ?? "",
7120
7175
  cookies: cookiesObj,
7121
7176
  config,
7122
- // 运行时资源根目录(HTTP/WS 链路由 createServer/handleWsUpgrade 传入;
7123
- // testing 直调未传时为 undefined,见 contextTypes.resourcesDir 注释)
7124
- resourcesDir,
7125
7177
  meta,
7126
7178
  setStatus(status) {
7127
7179
  meta.status = status;
@@ -7561,7 +7613,7 @@ function createAgentRegistry(tool) {
7561
7613
  if (!agent) return void 0;
7562
7614
  return {
7563
7615
  kind: "agent",
7564
- name: `agent.${agent.name}`,
7616
+ name: subAgentToolName(agent.name),
7565
7617
  agentName: agent.name,
7566
7618
  description: agent.description,
7567
7619
  metadata: agent
@@ -7699,6 +7751,7 @@ var init_registries = __esm({
7699
7751
  "src/injection/registries.ts"() {
7700
7752
  "use strict";
7701
7753
  init_taskRegistry();
7754
+ init_subAgentToolName();
7702
7755
  defaultRegistries = createAppRegistries();
7703
7756
  }
7704
7757
  });
@@ -8657,7 +8710,6 @@ function attachWebSocket(options) {
8657
8710
  trustedProxy = false,
8658
8711
  registries
8659
8712
  } = options;
8660
- const resourcesDir = resolveResourcesDir(rootDir, dist);
8661
8713
  const wss = new WebSocketServer({ noServer: true });
8662
8714
  server.on("upgrade", async (req, socket, head) => {
8663
8715
  try {
@@ -8689,14 +8741,7 @@ function attachWebSocket(options) {
8689
8741
  const host = req.headers.host ?? "localhost";
8690
8742
  const fullUrl = new URL(`http://${host}${req.url ?? "/"}`);
8691
8743
  const request = new Request(fullUrl.toString(), { method: "GET", headers });
8692
- const ctx = createContext(
8693
- request,
8694
- params,
8695
- config,
8696
- getClientIp(req, trustedProxy),
8697
- registries,
8698
- resourcesDir
8699
- );
8744
+ const ctx = createContext(request, params, config, getClientIp(req, trustedProxy), registries);
8700
8745
  const meta = ctx.meta;
8701
8746
  let upgraded = false;
8702
8747
  const finalHandler = async () => {
@@ -8798,7 +8843,6 @@ var init_handleWsUpgrade = __esm({
8798
8843
  "use strict";
8799
8844
  init_matchRoute();
8800
8845
  init_createContext();
8801
- init_copyResources();
8802
8846
  init_invokeHandler();
8803
8847
  init_importWithCacheBust();
8804
8848
  init_getClientIp();
@@ -8925,7 +8969,6 @@ function createServer(options) {
8925
8969
  trustedProxy = false
8926
8970
  } = options;
8927
8971
  const routesRef = { current: routes, wsCurrent: wsRoutes ?? [] };
8928
- const resourcesDir = resolveResourcesDir(rootDir, dist);
8929
8972
  const configMiddlewares = [];
8930
8973
  if (compressionOption) {
8931
8974
  const compOpts = typeof compressionOption === "object" ? compressionOption : {};
@@ -8972,8 +9015,7 @@ function createServer(options) {
8972
9015
  bodyLimit,
8973
9016
  trustedProxy,
8974
9017
  registries,
8975
- routesRef.wsCurrent,
8976
- resourcesDir
9018
+ routesRef.wsCurrent
8977
9019
  ).catch((err) => {
8978
9020
  console.error("[faapi] Request pipeline failed after error response attempt:", err);
8979
9021
  try {
@@ -8999,7 +9041,7 @@ function createServer(options) {
8999
9041
  });
9000
9042
  return { server, routesRef };
9001
9043
  }
9002
- function prepareRequest(req, request, url, config, trustedProxy, registries, preMatched, resourcesDir) {
9044
+ function prepareRequest(req, request, url, config, trustedProxy, registries, preMatched) {
9003
9045
  const method = request.method.toUpperCase();
9004
9046
  const urlPath = url.pathname;
9005
9047
  const ctx = createContextFromUrl(
@@ -9008,8 +9050,7 @@ function prepareRequest(req, request, url, config, trustedProxy, registries, pre
9008
9050
  preMatched?.params ?? {},
9009
9051
  config,
9010
9052
  getClientIp(req, trustedProxy),
9011
- registries,
9012
- resourcesDir
9053
+ registries
9013
9054
  );
9014
9055
  const meta = ctx.meta;
9015
9056
  return { request, url, ctx, meta, method, urlPath };
@@ -9149,7 +9190,7 @@ async function sendErrorResponse(err, meta, res, onError, ctx) {
9149
9190
  }
9150
9191
  }
9151
9192
  }
9152
- async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares, onError, config, globalInjectors, bodyLimit, trustedProxy, registries, wsRoutes, resourcesDir) {
9193
+ async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares, onError, config, globalInjectors, bodyLimit, trustedProxy, registries, wsRoutes) {
9153
9194
  let meta = { headers: {}, setCookies: [] };
9154
9195
  let ctx;
9155
9196
  let sseEarlySent = false;
@@ -9169,8 +9210,7 @@ async function handleRequest(routes, rootDir, dist, req, res, outerMiddlewares,
9169
9210
  config,
9170
9211
  trustedProxy,
9171
9212
  registries,
9172
- preMatched,
9173
- resourcesDir
9213
+ preMatched
9174
9214
  );
9175
9215
  ctx = prepared.ctx;
9176
9216
  meta = prepared.meta;
@@ -9239,7 +9279,6 @@ var init_createServer = __esm({
9239
9279
  init_handleWsUpgrade();
9240
9280
  init_serverUtils();
9241
9281
  init_generateSchemaFiles();
9242
- init_copyResources();
9243
9282
  init_compileOnDemand();
9244
9283
  init_loadMiddlewares();
9245
9284
  DEFAULT_BODY_LIMIT = 10 * 1024 * 1024;
@@ -9419,7 +9458,6 @@ async function runTaskInWorker(options) {
9419
9458
  payload,
9420
9459
  taskCtx: {
9421
9460
  config: safeConfig(taskCtx.config),
9422
- resourcesDir: taskCtx.resourcesDir,
9423
9461
  job: taskCtx.job
9424
9462
  },
9425
9463
  registries: options.registries,
@@ -9578,7 +9616,6 @@ function createTaskQueue(deps) {
9578
9616
  const taskCtx = {
9579
9617
  signal: job.signal,
9580
9618
  config: deps.config,
9581
- resourcesDir: deps.resourcesDir,
9582
9619
  job: { id: job.id, name: job.name, attempt: job.attempt },
9583
9620
  registries: registriesView,
9584
9621
  log: createLogger(`task:${job.name}`, {
@@ -10297,7 +10334,6 @@ async function createAppBase(options) {
10297
10334
  config?.plugins,
10298
10335
  {
10299
10336
  rootDir,
10300
- resourcesDir,
10301
10337
  registries,
10302
10338
  routes: sorted,
10303
10339
  getRoutes: () => sorted,
@@ -10328,8 +10364,7 @@ async function createAppBase(options) {
10328
10364
  routes: sorted,
10329
10365
  server,
10330
10366
  registries,
10331
- tasks: taskQueue,
10332
- resourcesDir
10367
+ tasks: taskQueue
10333
10368
  });
10334
10369
  console.log("- onBoot hook executed");
10335
10370
  } catch (err) {
@@ -10364,8 +10399,7 @@ async function createAppBase(options) {
10364
10399
  routes: sorted,
10365
10400
  server,
10366
10401
  registries,
10367
- tasks: taskQueue,
10368
- resourcesDir
10402
+ tasks: taskQueue
10369
10403
  });
10370
10404
  console.log("- onReady hook executed");
10371
10405
  }
@@ -10389,8 +10423,7 @@ async function createAppBase(options) {
10389
10423
  routes: sorted,
10390
10424
  server,
10391
10425
  registries,
10392
- tasks: taskQueue,
10393
- resourcesDir
10426
+ tasks: taskQueue
10394
10427
  });
10395
10428
  }
10396
10429
  registries.tool.clear();