@faapi/faapi 6.27.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.
@@ -51,7 +51,7 @@ interface ToolMetadata extends ToolCore {
51
51
  * 透传到 [ToolMetadata](./extractToolMetadata.ts) 输出,与 AST 提取字段合并。
52
52
  */
53
53
  interface ToolPathMeta {
54
- /** 路径推导的 tool 名(如 `weather.getWeather`) */
54
+ /** 路径推导的 tool 名(如 `weather_getWeather`) */
55
55
  name: string;
56
56
  /** 源码相对路径(如 `src/tools/weather/handler.ts`) */
57
57
  filePath: string;
@@ -432,7 +432,7 @@ declare function runTaskInWorker(options: TaskWorkerOptions): Promise<unknown>;
432
432
  interface ToolRegistry {
433
433
  /** 全量替换(tool 清单来自编译期产物,reload 时整体重新生成) */
434
434
  hydrate(tools: ToolMetadata[]): void;
435
- /** 按全名查找(如 `weather.getWeather`) */
435
+ /** 按全名查找(如 `weather_getWeather`) */
436
436
  get(name: string): ToolMetadata | undefined;
437
437
  /** 所有已注册 tool(副本) */
438
438
  list(): ToolMetadata[];
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { A as AppRegistries, F as FaapiContext, T as TaskClient, L as LogConfig, a as TaskFailedHandler, b as TaskQueueDeps, c as TaskQueue, d as TaskDriver, e as TaskRegistry, f as TaskManifest, g as ToolMetadata, h as AgentCore, i as AgentMetadata, C as CreateLoggerOptions, j as Logger } from './contextTypes-CBE86AO2.js';
2
- export { k as AgentHandleFactory, l as AgentHandleStore, m as AgentPathMeta, n as AgentRegistry, o as AgentToolDescriptor, p as FaapiContextConfig, q as FaapiTaskMeta, r as FailOptions, s as LogEntry, t as LogLevel, u as LogSink, S as SkillRegistry, v as SseEvent, w as SseOptions, x as SseWriter, y as TaskContext, z as TaskDriverJob, B as TaskDriverProcess, D as TaskDriverRecord, E as TaskFailedInfo, G as TaskJob, H as TaskJobStatus, I as TaskMetadata, J as TaskModule, K as TaskRegistriesSnapshot, M as TaskRegistriesView, N as ToolCore, O as ToolPathMeta, P as ToolRegistry, Q as createAppRegistries, R as createTaskRegistriesView, U as createTaskRegistry } from './contextTypes-CBE86AO2.js';
3
- import { F as FaapiMiddleware, R as RouteManifest, C as CorsOptions, H as HelmetOptions, I as InjectorMap, W as WsRouteManifest } from './routeTypes-DVSnH0jk.js';
4
- export { a as Injector, b as RouteInfo, c as RouteInputSchema, d as RouteOutputSchema, e as RouteParamSchema, f as cors, h as helmet } from './routeTypes-DVSnH0jk.js';
1
+ import { A as AppRegistries, F as FaapiContext, T as TaskClient, L as LogConfig, a as TaskFailedHandler, b as TaskQueueDeps, c as TaskQueue, d as TaskDriver, e as TaskRegistry, f as TaskManifest, g as ToolMetadata, h as AgentCore, i as AgentMetadata, C as CreateLoggerOptions, j as Logger } from './contextTypes-DhagvRH_.js';
2
+ export { k as AgentHandleFactory, l as AgentHandleStore, m as AgentPathMeta, n as AgentRegistry, o as AgentToolDescriptor, p as FaapiContextConfig, q as FaapiTaskMeta, r as FailOptions, s as LogEntry, t as LogLevel, u as LogSink, S as SkillRegistry, v as SseEvent, w as SseOptions, x as SseWriter, y as TaskContext, z as TaskDriverJob, B as TaskDriverProcess, D as TaskDriverRecord, E as TaskFailedInfo, G as TaskJob, H as TaskJobStatus, I as TaskMetadata, J as TaskModule, K as TaskRegistriesSnapshot, M as TaskRegistriesView, N as ToolCore, O as ToolPathMeta, P as ToolRegistry, Q as createAppRegistries, R as createTaskRegistriesView, U as createTaskRegistry } from './contextTypes-DhagvRH_.js';
3
+ import { F as FaapiMiddleware, R as RouteManifest, C as CorsOptions, H as HelmetOptions, I as InjectorMap, W as WsRouteManifest } from './routeTypes-0rwuIws-.js';
4
+ export { a as Injector, b as RouteInfo, c as RouteInputSchema, d as RouteOutputSchema, e as RouteParamSchema, f as cors, h as helmet } from './routeTypes-0rwuIws-.js';
5
5
  import * as node_http from 'node:http';
6
6
  import { Server, IncomingMessage, ServerResponse } from 'node:http';
7
7
  import { Socket } from 'node:net';
@@ -1348,11 +1348,40 @@ declare function resolveSubAgents(name: string): AgentCore[];
1348
1348
  *
1349
1349
  * 框架路径(`@faapi/agent` 插件 / 请求注入)从 app 实例查找。
1350
1350
  *
1351
- * @param tool 全名(如 `weather.getWeather`)
1351
+ * @param tool 全名(如 `weather_getWeather`)
1352
1352
  * @returns `ToolMetadata` 或 `undefined`(未注册)
1353
1353
  */
1354
1354
  declare function getTool(name: string): ToolMetadata | undefined;
1355
1355
 
1356
+ /**
1357
+ * sub-agent 派发工具名生成(唯一入口)
1358
+ *
1359
+ * agent-as-tool 把 sub-agent 包装成 tool 发给 LLM,`function.name` 受 OpenAI 兼容协议
1360
+ * 硬约束 `^[a-zA-Z0-9_-]+$`——DeepSeek / OpenAI 等强校验上游对非法字符整单 400。
1361
+ * 历史上派发名是 `agent.<agentName>`(点号前缀 + 嵌套 agent 名的点号分隔),在强校验
1362
+ * 上游完全不可用(业务方反馈 TODO-faapi-gaps #1)。
1363
+ *
1364
+ * 命名规则:`agent-` 前缀 + agent 名直拼。agent 名的合法性由上游保证——文件型 agent
1365
+ * 在 scanAgents 目录段校验(段内 `[a-zA-Z0-9-]`,`_` 为 `/` 规范化后的嵌套分隔符)、
1366
+ * `@agent` 覆盖名在 extractAgentMetadata 校验(整体 `^[a-zA-Z0-9_-]+$`);本模块对组合
1367
+ * 结果做最后一道运行时闸门(程序化 hydrate 的 agent 名不经构建期扫描),违例显式抛错
1368
+ * ——不静默净化(掩盖配置错误),不降级放行(强校验上游 400)。
1369
+ *
1370
+ * 详见 [subAgentToolName.md](./subAgentToolName.md)。
1371
+ */
1372
+ /** OpenAI 兼容协议对 LLM 工具名(function.name)的字符集约束 */
1373
+ declare const LLM_TOOL_NAME_PATTERN: RegExp;
1374
+ /** sub-agent 派发工具名前缀(旧版 `agent.` 点号前缀违反上述字符集,已废弃) */
1375
+ declare const SUB_AGENT_TOOL_PREFIX = "agent-";
1376
+ /**
1377
+ * 生成 sub-agent 派发工具名:`agent-<agentName>`
1378
+ *
1379
+ * @param agentName agent 注册名(scanAgents 目录推导或 `@agent` 覆盖名)
1380
+ * @returns 满足 {@link LLM_TOOL_NAME_PATTERN} 的派发工具名
1381
+ * @throws agent 名含工具名字符集之外的字符时抛错(含原名与改名指引)
1382
+ */
1383
+ declare function subAgentToolName(agentName: string): string;
1384
+
1356
1385
  /**
1357
1386
  * skill 注册表全局访问器(默认实例便捷入口)
1358
1387
  *
@@ -1743,4 +1772,4 @@ type ProdApp = AppBase;
1743
1772
  */
1744
1773
  declare function createProdApp(options?: CreateAppOptions): Promise<ProdApp>;
1745
1774
 
1746
- export { type AgentConfig, AgentCore, AgentMetadata, type ProdApp as App, AppRegistries, CorsOptions, type CreateAppOptions, CreateLoggerOptions, type CronScheduler, type DevApp, type FaapiConfig, FaapiContext, FaapiError, FaapiMiddleware, type FaapiPlugin, type HandlerTypeInfo, HelmetOptions, type InjectOptions, type InjectResponse, InjectorMap, InternalError, type LifecycleContext, type LifecycleHooks, type LlmConfig, type LlmModelConfig, LogConfig, Logger, type LoggerOptions, MethodNotAllowedError, ModuleLoadError, type PluginContext, type PluginDeclaration, type ProdApp, type PropertyType, type RequestHandler, type ResponseConfig, RouteManifest, RouteNotFoundError, type RouteSchemaSource, type RuntimeType, SchemaExtractionError, TASK_PATTERNS, type TaskBullMqOptions, TaskClient, type TaskConfig, TaskDriver, TaskFailedHandler, TaskManifest, type TaskPgBossOptions, TaskQueue, TaskRegistry, ToolMetadata, type ToolModule, type ToolSchemaModule, type TypeConstraint, type UpgradeHandler, ValidationError, type ValidationErrorCode, type ValidationIssue, type WsContext, type WsEventHandlers, type WsHandler, type WsSocket, clearAgentHandleFactory, collectRouteSchemaSources, configureLogging, createProdApp as createApp, createCronScheduler, createDevApp, createLogger, createProdApp, createProgram, createPrograms, createTaskQueue, extractTypeInfo, flushLogging, getAgent, getAgentEntry, getApp, getInputTypeForMethod, getSkill, getTool, getToolSchemaPath, hydrateSkillRegistry, invalidateProgramCache, listSkills, loadConfig, loadEnv, loadTaskDriver, loadToolModule, loadToolSchema, logger, readResource, registerAgentHandleFactory, removeSkill, resolveAgentTools, resolveSubAgents, resolveTypeNode, scanTasks, upsertSkill };
1775
+ export { type AgentConfig, AgentCore, AgentMetadata, type ProdApp as App, AppRegistries, CorsOptions, type CreateAppOptions, CreateLoggerOptions, type CronScheduler, type DevApp, type FaapiConfig, FaapiContext, FaapiError, FaapiMiddleware, type FaapiPlugin, type HandlerTypeInfo, HelmetOptions, type InjectOptions, type InjectResponse, InjectorMap, InternalError, LLM_TOOL_NAME_PATTERN, type LifecycleContext, type LifecycleHooks, type LlmConfig, type LlmModelConfig, LogConfig, Logger, type LoggerOptions, MethodNotAllowedError, ModuleLoadError, type PluginContext, type PluginDeclaration, type ProdApp, type PropertyType, type RequestHandler, type ResponseConfig, RouteManifest, RouteNotFoundError, type RouteSchemaSource, type RuntimeType, SUB_AGENT_TOOL_PREFIX, SchemaExtractionError, TASK_PATTERNS, type TaskBullMqOptions, TaskClient, type TaskConfig, TaskDriver, TaskFailedHandler, TaskManifest, type TaskPgBossOptions, TaskQueue, TaskRegistry, ToolMetadata, type ToolModule, type ToolSchemaModule, type TypeConstraint, type UpgradeHandler, ValidationError, type ValidationErrorCode, type ValidationIssue, type WsContext, type WsEventHandlers, type WsHandler, type WsSocket, clearAgentHandleFactory, collectRouteSchemaSources, configureLogging, createProdApp as createApp, createCronScheduler, createDevApp, createLogger, createProdApp, createProgram, createPrograms, createTaskQueue, extractTypeInfo, flushLogging, getAgent, getAgentEntry, getApp, getInputTypeForMethod, getSkill, getTool, getToolSchemaPath, hydrateSkillRegistry, invalidateProgramCache, listSkills, loadConfig, loadEnv, loadTaskDriver, loadToolModule, loadToolSchema, logger, readResource, registerAgentHandleFactory, removeSkill, resolveAgentTools, resolveSubAgents, resolveTypeNode, scanTasks, subAgentToolName, upsertSkill };
package/dist/index.js CHANGED
@@ -1835,6 +1835,19 @@ function createTaskRegistry() {
1835
1835
  };
1836
1836
  }
1837
1837
 
1838
+ // src/injection/subAgentToolName.ts
1839
+ var LLM_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
1840
+ var SUB_AGENT_TOOL_PREFIX = "agent-";
1841
+ function subAgentToolName(agentName) {
1842
+ const toolName = SUB_AGENT_TOOL_PREFIX + agentName;
1843
+ if (!LLM_TOOL_NAME_PATTERN.test(toolName)) {
1844
+ throw new Error(
1845
+ `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)`
1846
+ );
1847
+ }
1848
+ return toolName;
1849
+ }
1850
+
1838
1851
  // src/injection/registries.ts
1839
1852
  function createToolRegistry() {
1840
1853
  let registry = /* @__PURE__ */ new Map();
@@ -1882,7 +1895,7 @@ function createAgentRegistry(tool) {
1882
1895
  if (!agent) return void 0;
1883
1896
  return {
1884
1897
  kind: "agent",
1885
- name: `agent.${agent.name}`,
1898
+ name: subAgentToolName(agent.name),
1886
1899
  agentName: agent.name,
1887
1900
  description: agent.description,
1888
1901
  metadata: agent
@@ -3685,6 +3698,7 @@ function extractJSDocTagValue(jsDoc, tagName) {
3685
3698
  }
3686
3699
 
3687
3700
  // src/ast/extractToolMetadata.ts
3701
+ init_resolveTypeNode();
3688
3702
  function extractToolMetadata(program, filePath, functionName, pathMeta) {
3689
3703
  const sourceFile = program.getSourceFile(filePath);
3690
3704
  if (!sourceFile) return null;
@@ -3694,6 +3708,14 @@ function extractToolMetadata(program, filePath, functionName, pathMeta) {
3694
3708
  const jsDoc = getJSDocFromNode(jsDocOwner);
3695
3709
  const description = extractDescription(jsDoc);
3696
3710
  const toolNameOverride = extractJSDocTagValue(jsDoc, "tool");
3711
+ if (toolNameOverride !== void 0 && !LLM_TOOL_NAME_PATTERN.test(toolNameOverride)) {
3712
+ throw SchemaExtractionError.at(
3713
+ jsDocOwner,
3714
+ "@tool",
3715
+ `\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`,
3716
+ sourceFile
3717
+ );
3718
+ }
3697
3719
  const inputTypeName = getFirstParamTypeName(fn, sourceFile);
3698
3720
  return {
3699
3721
  name: toolNameOverride ?? pathMeta.name,
@@ -6825,6 +6847,14 @@ function extractAgentMetadata(program, filePath, pathMeta) {
6825
6847
  const jsDoc = getJSDocFromNode(configFound.jsDocOwner);
6826
6848
  const description = extractDescription(jsDoc);
6827
6849
  const agentNameOverride = extractJSDocTagValue(jsDoc, "agent");
6850
+ if (agentNameOverride !== void 0 && !LLM_TOOL_NAME_PATTERN.test(agentNameOverride)) {
6851
+ throw SchemaExtractionError.at(
6852
+ configFound.jsDocOwner,
6853
+ "@agent",
6854
+ `\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`,
6855
+ sourceFile
6856
+ );
6857
+ }
6828
6858
  const { systemPrompt, systemPromptFile, tools, agents, model, maxTurns, inputDescription } = extractConfigFields(objectLiteral, sourceFile);
6829
6859
  return {
6830
6860
  name: agentNameOverride ?? pathMeta.name,
@@ -7897,11 +7927,20 @@ function extractToolExportsFromSource(source) {
7897
7927
  }
7898
7928
  return names;
7899
7929
  }
7930
+ var TOOL_DIR_SEGMENT_PATTERN = /^[a-zA-Z0-9-]+$/;
7900
7931
  function extractNamespaceFromRelPath(relPath) {
7901
7932
  const lastSlash = relPath.lastIndexOf("/");
7902
7933
  const dirPath = lastSlash === -1 ? "" : relPath.slice(0, lastSlash);
7903
7934
  if (!dirPath) return "";
7904
- return dirPath.split("/").join(".");
7935
+ const segments = dirPath.split("/");
7936
+ for (const segment of segments) {
7937
+ if (!TOOL_DIR_SEGMENT_PATTERN.test(segment)) {
7938
+ throw new Error(
7939
+ `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`
7940
+ );
7941
+ }
7942
+ }
7943
+ return segments.join("_");
7905
7944
  }
7906
7945
  function filePathToToolNamespace(filePath) {
7907
7946
  const normalized = filePath.replace(/\\/g, "/");
@@ -7912,7 +7951,13 @@ function filePathToToolNamespace(filePath) {
7912
7951
  return "";
7913
7952
  }
7914
7953
  function buildToolName(namespace, functionName) {
7915
- return namespace ? `${namespace}.${functionName}` : functionName;
7954
+ const name = namespace ? `${namespace}_${functionName}` : functionName;
7955
+ if (!LLM_TOOL_NAME_PATTERN.test(name)) {
7956
+ throw new Error(
7957
+ `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 '-' '_')`
7958
+ );
7959
+ }
7960
+ return name;
7916
7961
  }
7917
7962
  async function scanTools(rootDir, patterns) {
7918
7963
  const files = await fg4(patterns, {
@@ -7954,6 +7999,7 @@ async function scanTools(rootDir, patterns) {
7954
7999
  // src/agents/scanAgents.ts
7955
8000
  import fg5 from "fast-glob";
7956
8001
  var DEFAULT_AGENT_PATTERNS = ["src/agents/**/handler.ts"];
8002
+ var AGENT_DIR_SEGMENT_PATTERN = /^[a-zA-Z0-9-]+$/;
7957
8003
  function extractAgentNameFromPath(filePath) {
7958
8004
  const normalized = filePath.replace(/\\/g, "/");
7959
8005
  const match = normalized.match(/(?:^|\/)agents\/(.+)\/handler\.ts$/);
@@ -7962,7 +8008,15 @@ function extractAgentNameFromPath(filePath) {
7962
8008
  `Not an agent path: "${filePath}". Expected pattern: src/agents/<name>/handler.ts`
7963
8009
  );
7964
8010
  }
7965
- return match[1].replace(/\//g, ".");
8011
+ const segments = match[1].split("/");
8012
+ for (const segment of segments) {
8013
+ if (!AGENT_DIR_SEGMENT_PATTERN.test(segment)) {
8014
+ throw new Error(
8015
+ `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`
8016
+ );
8017
+ }
8018
+ }
8019
+ return segments.join("_");
7966
8020
  }
7967
8021
  async function scanAgents(rootDir, patterns) {
7968
8022
  const files = await fg5(patterns, {
@@ -8078,9 +8132,11 @@ async function createProdApp(options) {
8078
8132
  export {
8079
8133
  FaapiError,
8080
8134
  InternalError,
8135
+ LLM_TOOL_NAME_PATTERN,
8081
8136
  MethodNotAllowedError,
8082
8137
  ModuleLoadError,
8083
8138
  RouteNotFoundError,
8139
+ SUB_AGENT_TOOL_PREFIX,
8084
8140
  SchemaExtractionError,
8085
8141
  TASK_PATTERNS,
8086
8142
  ValidationError,
@@ -8125,6 +8181,7 @@ export {
8125
8181
  resolveSubAgents,
8126
8182
  resolveTypeNode,
8127
8183
  scanTasks,
8184
+ subAgentToolName,
8128
8185
  upsertSkill
8129
8186
  };
8130
8187
  //# sourceMappingURL=index.js.map