@faapi/faapi 3.3.0 → 4.0.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.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as RouteManifest, C as CorsOptions, F as FaapiContext, H as HelmetOptions, L as LoggerOptions, a as FaapiMiddleware, I as InjectorMap, W as WsRouteManifest } from './routeTypes-FtbRkpVF.js';
2
- export { b as FaapiContextConfig, c as FailOptions, d as Injector, e as RouteInfo, f as RouteInputSchema, g as RouteOutputSchema, h as RouteParamSchema, S as SseEvent, i as SseWriter, j as cors, k as helmet, l as logger } from './routeTypes-FtbRkpVF.js';
1
+ import { A as AppRegistries, R as RouteManifest, F as FaapiContext, C as CorsOptions, H as HelmetOptions, L as LoggerOptions, a as FaapiMiddleware, I as InjectorMap, T as ToolMetadata, b as AgentCore, c as AgentMetadata, W as WsRouteManifest } from './routeTypes-DnJuuvq-.js';
2
+ export { d as AgentHandleFactory, e as AgentHandleStore, f as AgentPathMeta, g as AgentRegistry, h as AgentToolDescriptor, i as FaapiContextConfig, j as FailOptions, k as Injector, l as RouteInfo, m as RouteInputSchema, n as RouteOutputSchema, o as RouteParamSchema, S as SkillRegistry, p as SseEvent, q as SseWriter, r as ToolCore, s as ToolPathMeta, t as ToolRegistry, u as cors, v as createAppRegistries, w as helmet, x as logger } from './routeTypes-DnJuuvq-.js';
3
3
  import * as node_http from 'node:http';
4
4
  import { Server, IncomingMessage, ServerResponse } from 'node:http';
5
5
  import { Socket } from 'node:net';
@@ -20,6 +20,8 @@ type UpgradeHandler = (req: IncomingMessage, socket: Socket, head: Buffer) => vo
20
20
  interface PluginContext {
21
21
  /** 项目根目录 */
22
22
  rootDir: string;
23
+ /** app 级注册表(tool/agent/skill/agentHandle 实例,随 app 生命周期) */
24
+ registries: AppRegistries;
23
25
  /** 当前路由清单(setup 时的快照,reloadRoutes 后不会更新;需最新路由用 getRoutes()) */
24
26
  routes: RouteManifest;
25
27
  /** 获取最新路由清单(reloadRoutes 后返回更新后的数组) */
@@ -94,6 +96,23 @@ type PluginDeclaration = string | [string, unknown] | {
94
96
  options?: unknown;
95
97
  };
96
98
 
99
+ interface CompressionOptions {
100
+ /**
101
+ * 最小压缩字节数,body 低于该值不压缩(小 payload 压缩后反而变大)
102
+ * @default 1024
103
+ */
104
+ threshold?: number;
105
+ }
106
+
107
+ interface EtagOptions {
108
+ /**
109
+ * 生成弱 ETag(`W/"<hash>"`)。弱校验器允许压缩等表示差异下的 304 协商,
110
+ * 与 compression 中间件配合正确;强 ETag 需对每个表示(编码)单独生成
111
+ * @default true
112
+ */
113
+ weak?: boolean;
114
+ }
115
+
97
116
  interface Http2Options {
98
117
  key?: string;
99
118
  cert?: string;
@@ -132,6 +151,8 @@ interface LifecycleContext {
132
151
  routes: RouteManifest;
133
152
  /** 服务器实例 */
134
153
  server: node_http.Server;
154
+ /** app 级注册表——skill 等运行时动态注册路径(`registries.skill.upsert(...)`) */
155
+ registries: AppRegistries;
135
156
  }
136
157
  /**
137
158
  * 统一响应包装配置
@@ -249,6 +270,21 @@ interface LlmConfig {
249
270
  * model 级字段(如 `temperature`)覆盖 provider 级同名字段。
250
271
  */
251
272
  models: Record<string, LlmModelConfig>;
273
+ /**
274
+ * LLM 请求超时(毫秒,可选——未设置时无超时)
275
+ *
276
+ * provider 层用 `AbortSignal.timeout` 实现,与 run-level 的 `signal` 组合生效。
277
+ * 超时触发抛 `LLMProviderError`(message 含 timed out),计入重试(429/5xx/网络错误同策略)。
278
+ */
279
+ timeoutMs?: number;
280
+ /**
281
+ * 429 / 5xx / 网络错误的最大重试次数(默认 2,设 0 关闭重试)
282
+ *
283
+ * 退避策略:优先尊重响应的 `Retry-After` 头(秒,封顶 30s),否则指数退避
284
+ * 500ms * 2^attempt。4xx 其他状态(400/401 等)是确定性错误,不重试。
285
+ * 流式请求仅在「连接建立前」重试,流开始输出后中断不重试。
286
+ */
287
+ maxRetries?: number;
252
288
  /**
253
289
  * 其他透传参数(provider 级,如 temperature / top_p / max_tokens)
254
290
  *
@@ -351,6 +387,48 @@ interface AgentConfig {
351
387
  * 详见 `@faapi/agent` 的 [trace](../../agent/src/trace.md) 文档。
352
388
  */
353
389
  enableTracing?: boolean;
390
+ /**
391
+ * 执行守卫(authHooks,见 @faapi/agent 的 authHooks 文档)
392
+ *
393
+ * 每次 tool / sub-agent 执行前调用(`agent.x` 名称为 sub-agent 递归)。
394
+ * 三种返回:`void` 放行;`{ error }` 拒绝(不执行,error 回传 LLM 调整策略);
395
+ * `{ args }` 改写后放行(多租户场景强制注入可信值,不信 LLM 传入的标识参数)。
396
+ *
397
+ * 典型用法:中间件解析 `ctx.workspace` 后在此校验/强制改写 `args.workspaceId`。
398
+ */
399
+ /**
400
+ * 发送给 LLM 的历史 token 预算(近似估算,未设置 = 不裁剪)
401
+ *
402
+ * 多轮 tool 循环中对话历史只增不减,大 tool 结果会撑爆模型上下文窗口导致
403
+ * 下一轮 400。超预算时从最旧的轮组开始裁剪(system 与初始 user 保留、
404
+ * tool 配对不拆散),只作用于发给 LLM 的消息副本。详见 @faapi/agent 的
405
+ * reactLoop 文档历史裁剪章节。
406
+ */
407
+ maxHistoryTokens?: number;
408
+ beforeToolCall?: (name: string, args: Record<string, unknown>, ctx: FaapiContext | undefined) => void | {
409
+ error: string;
410
+ } | {
411
+ args: Record<string, unknown>;
412
+ };
413
+ /**
414
+ * 审计钩子(authHooks):tool / sub-agent 成功返回后调用,返回值忽略。
415
+ * 用于日志/审计/计量;异常路径不调用。
416
+ */
417
+ afterToolCall?: (name: string, args: Record<string, unknown>, result: unknown, ctx: FaapiContext | undefined) => void;
418
+ /**
419
+ * 可见性过滤(authHooks):LLM 可见 tools 清单组装完成后调用,
420
+ * 返回过滤后的数组(含 agent-as-tool 项)。每次 agent.run / stream 生效——
421
+ * 无权 tool 不进 LLM 视野,比执行时拒绝省一轮 LLM 调用。
422
+ */
423
+ filterTools?: (tools: Array<{
424
+ name: string;
425
+ description?: string;
426
+ input: Record<string, unknown>;
427
+ }>, ctx: FaapiContext | undefined) => Array<{
428
+ name: string;
429
+ description?: string;
430
+ input: Record<string, unknown>;
431
+ }>;
354
432
  }
355
433
  /**
356
434
  * faapi 配置文件类型
@@ -388,6 +466,16 @@ interface FaapiConfig {
388
466
  lifecycle?: LifecycleHooks;
389
467
  /** 安全头配置,false 禁用 */
390
468
  helmet?: HelmetOptions | boolean;
469
+ /**
470
+ * 响应压缩(gzip/deflate/br 协商),默认关闭。
471
+ * Vary: Accept-Encoding 自动附加;SSE/流式响应跳过,详见 middleware/compression.md
472
+ */
473
+ compression?: CompressionOptions | boolean;
474
+ /**
475
+ * ETag/304 条件请求协商(GET/HEAD 2xx 弱 ETag),默认关闭。
476
+ * handler 显式 ctx.setETag() 时不覆盖,详见 middleware/etag.md
477
+ */
478
+ etag?: EtagOptions | boolean;
391
479
  /** 请求体大小限制(字节),默认 10MB(10 * 1024 * 1024) */
392
480
  bodyLimit?: number;
393
481
  /** 日志中间件配置 */
@@ -657,9 +745,11 @@ declare function createProgram(filePath: string): ts.Program;
657
745
  * 完全一致,但 N 个文件只创建 1 个 Program(原来每个文件都全量解析一遍项目源码,
658
746
  * 是 build 时间的最大单项开销)。
659
747
  *
660
- * 缓存 key 为 `shared::<tsconfigPath>::<排序后的文件列表>`:同一批次重复调用命中
661
- * 缓存;不同批次(rootNames 不同)各自创建。`invalidateProgramCache()` 同时清理
662
- * 共享缓存与单文件缓存。
748
+ * 缓存 key 为 `shared::<tsconfigPath>`(同一 tsconfig 一份 Program):同一批次重复
749
+ * 调用命中缓存;不同批次通过 `program.getSourceFile` 校验缓存已覆盖本次全部入口文件,
750
+ * 未覆盖(入口在 tsconfig include 之外)时重建。若把文件列表纳入 key,dev 按需模式下
751
+ * 每个路由文件会各自持有一份全项目 Program(内存 O(路由数 × 项目大小))。
752
+ * `invalidateProgramCache()` 同时清理共享缓存与单文件缓存。
663
753
  *
664
754
  * 向上查找不到 tsconfig.json 的文件(如 os.tmpdir() 测试场景)逐个回退到
665
755
  * {@link createProgram} 单文件行为,不参与共享。
@@ -675,10 +765,24 @@ declare function createPrograms(filePaths: string[]): Map<string, ts.Program>;
675
765
  * 遇到无法解析或不支持运行时校验的类型时抛出,
676
766
  * 避免静默降级为 any 导致用户不知情。
677
767
  */
768
+ /** 错误在源文件中的位置(file:line:column,便于在几百行类型文件中定位) */
769
+ interface SchemaErrorLocation {
770
+ file: string;
771
+ line: number;
772
+ column: number;
773
+ }
678
774
  declare class SchemaExtractionError extends Error {
679
775
  readonly typeText: string;
680
776
  readonly reason: string;
681
- constructor(typeText: string, reason: string, options?: ErrorOptions);
777
+ readonly location?: SchemaErrorLocation | undefined;
778
+ constructor(typeText: string, reason: string, options?: ErrorOptions, location?: SchemaErrorLocation | undefined);
779
+ /**
780
+ * 从 AST 节点构造错误(自动携带 file:line:column)
781
+ *
782
+ * 所有抛错点应优先使用此工厂——错误无行号时,几百行的类型文件只能靠
783
+ * 类型名肉眼定位;解析 lib.d.ts 类型别名时还会出现错误文本与文件上下文错位
784
+ */
785
+ static at(node: ts.Node, typeText: string, reason: string): SchemaExtractionError;
682
786
  }
683
787
  /**
684
788
  * 运行时类型描述
@@ -714,6 +818,7 @@ type RuntimeType = {
714
818
  } | {
715
819
  kind: 'object';
716
820
  properties: PropertyType[];
821
+ catchall?: RuntimeType;
717
822
  } | {
718
823
  kind: 'union';
719
824
  members: RuntimeType[];
@@ -820,8 +925,11 @@ type TypeConstraint = {
820
925
  * @param typeNode TypeScript 类型节点
821
926
  * @param checker 类型 checker(用于解析引用类型)
822
927
  * @param visited 防止递归循环
928
+ * @param bindings 泛型形参绑定(形参名 → 已解析的实参类型),解析泛型声明的
929
+ * 类型体时传入;形参名在 resolveTypeReference 入口优先命中,
930
+ * 遮蔽同名的真实类型声明
823
931
  */
824
- declare function resolveTypeNode(typeNode: ts.TypeNode, checker?: ts.TypeChecker, visited?: Set<string>): RuntimeType;
932
+ declare function resolveTypeNode(typeNode: ts.TypeNode, checker?: ts.TypeChecker, visited?: Set<string>, bindings?: Map<string, RuntimeType>): RuntimeType;
825
933
 
826
934
  interface HandlerTypeInfo {
827
935
  name: string;
@@ -835,6 +943,14 @@ interface HandlerTypeInfo {
835
943
  * 支持的类型声明:
836
944
  * - interface 声明(含继承)
837
945
  * - type 别名(type Query = { ... })
946
+ * - enum 声明(跨文件回退路径支持)
947
+ *
948
+ * 自身文件找不到目标声明时,回退到 program 的其他源文件查找同名顶层声明
949
+ * (跳过 node_modules / TypeScript lib,首个匹配生效,与 resolveImportAlias
950
+ * 兜底路径语义一致)。该回退服务于入口类型中第二次出现的跨文件引用:首次引用
951
+ * 已由 checker 内联,二次引用被标记为 ref,代码生成阶段经
952
+ * createLazyTypeResolver → extractTypeInfo 解析,若无回退会静默生成
953
+ * z.unknown(),违背「不降级放行」约定。
838
954
  *
839
955
  * 遇到不支持的类型时抛 `SchemaExtractionError`,错误信息包含文件路径和类型名。
840
956
  *
@@ -843,6 +959,23 @@ interface HandlerTypeInfo {
843
959
  * @param typeName 类型名,如 'GETQuery'
844
960
  */
845
961
  declare function extractTypeInfo(program: ts.Program, filePath: string, typeName: string): HandlerTypeInfo | null;
962
+ /**
963
+ * 惰性类型解析器
964
+ *
965
+ * 按名称解析类型并缓存。与 extractAllTypes 的差异:extractAllTypes 提前解析
966
+ * 文件中全部顶层类型——与路由无关的类型也会被解析,其中任何类型含不支持语法
967
+ * 都会拖垮整个 build/reload;惰性解析器只在类型真正被需要时(入口类型或
968
+ * ref 引用)才解析,无关类型零开销。
969
+ */
970
+ interface LazyTypeResolver {
971
+ /**
972
+ * 按名称解析类型
973
+ *
974
+ * @returns 类型信息;文件中无该名称的声明时返回 null
975
+ * @throws SchemaExtractionError 类型声明含不支持语法时(与 extractTypeInfo 一致,不降级)
976
+ */
977
+ resolve(name: string): HandlerTypeInfo | null;
978
+ }
846
979
 
847
980
  /**
848
981
  * 根据 HTTP 方法判断主输入类型
@@ -887,256 +1020,21 @@ interface RouteSchemaSource {
887
1020
  *
888
1021
  * dev 和 prd 共享的核心提取流程:
889
1022
  * 1. 按文件分组遍历路由
890
- * 2. 对每个文件 createProgram + extractAllTypes 收集所有类型
891
- * 3. analyzeInjection + extractTypeInfo 提取每个路由的 schema 类型
892
- * 4. 同时返回按文件分组的 allTypesMap 和合并后的全局 allTypes
893
- *
894
- * 调用方基于返回的 sources 和 allTypes 各自做最终转换:
895
- * - dev:生成 JS 模块文件 → import 加载(用 allTypesByFile)
896
- * - prd:生成 JS 模块代码 → SchemaModuleEntry[](用 allTypesByFile)
1023
+ * 2. 批量共享 Program(同一次提取只按文件组创建少量 Program)
1024
+ * 3. 对每个路由用 analyzeInjectionInSourceFile 定位入口参数类型,
1025
+ * extractTypeInfo 解析入口类型(入口类型必须严格解析,失败抛 SchemaExtractionError)
1026
+ * 4. 返回按文件分组的惰性类型解析器——generateSchemaFiles 生成 zod.js 时
1027
+ * 遇到 ref(同文件循环引用)按需解析并缓存
1028
+ *
1029
+ * 惰性语义:与路由无关的类型(未被任何入口类型引用)不会被解析——文件里
1030
+ * 存在一个含不支持语法的无关类型不再拖垮整个 build/reload。
897
1031
  */
898
1032
  declare function collectRouteSchemaSources(routes: RouteManifest, rootDir?: string): {
899
1033
  sources: RouteSchemaSource[];
900
- /** 按文件分组的类型映射(prd writeSchemaModule 用) */
901
- allTypesByFile: Map<string, Map<string, HandlerTypeInfo>>;
902
- /** 合并后的全局类型映射(兼容旧调用方保留,新路径使用 allTypesByFile) */
903
- mergedAllTypes: Map<string, HandlerTypeInfo>;
1034
+ /** 按文件分组的惰性类型解析器(generateSchemaFiles 解析 ref 用) */
1035
+ resolversByFile: Map<string, LazyTypeResolver>;
904
1036
  };
905
1037
 
906
- /**
907
- * Agent 的 LLM 可见核心字段
908
- *
909
- * 描述"agent 是什么"——LLM 真正需要消费的字段,**不含**代码本体加载细节
910
- * (filePath / hasRun)。文件型 agent 与 DB-driven skill 都实现此接口。
911
- *
912
- * - 文件型 agent:由 [AgentMetadata](./extractAgentMetadata.md) 继承扩展,
913
- * 额外含 `filePath` / `hasRun`(代码本体加载用)
914
- * - DB-driven skill:业务方 plugin 从 DB 字段映射到本接口即可,无需填占位值
915
- * (skill 无源文件,不走 `loadAgentModule`,自然不读 filePath / hasRun)
916
- *
917
- * `@faapi/agent` 子包的 `Agent` 类、`agentRegistry` 查询入口、`asTool` 包装
918
- * 都消费 `AgentCore`,实现"agent 与 skill 走同一运行时链路"。
919
- */
920
- interface AgentCore {
921
- /** agent 名(`@agent` JSDoc 覆盖值 或 目录推导值) */
922
- name: string;
923
- /** JSDoc 描述(agent 描述,对 LLM 可见),无 JSDoc 或 JSDoc 无自由文本时为 `undefined` */
924
- description?: string;
925
- /** 系统提示词(config 块字面量提取),无/非字面量时为 `undefined` */
926
- systemPrompt?: string;
927
- /** agent 显式声明可用的 tool 引用列表(config 块字面量提取),无/含非字面量元素时为 `undefined` */
928
- tools?: string[];
929
- /** 可调用的其他 agent 名列表(config 块字面量提取),无/含非字面量元素时为 `undefined` */
930
- agents?: string[];
931
- /** LLM 模型名(config 块字面量提取),无/非字面量时为 `undefined` */
932
- model?: string;
933
- /** 最大对话轮数(config 块字面量提取),无/非字面量时为 `undefined` */
934
- maxTurns?: number;
935
- }
936
- /**
937
- * Agent 完整元数据(文件型 agent)
938
- *
939
- * 继承 [AgentCore](./extractAgentMetadata.md) 的 LLM 字段,额外扩展**代码本体加载细节**:
940
- * - `filePath` — `loadAgentModule` 加载 `handler.js` 产物提取 `run` 函数用
941
- * - `hasRun` — 是否导出 `run` 函数(`Agent.executeSubAgent` 据此决定走自定义 run
942
- * 还是默认 reactLoop)
943
- *
944
- * DB-driven skill 不实现此接口(无源文件,无需加载),只实现 `AgentCore`。
945
- *
946
- * 由 [extractAgentMetadata](./extractAgentMetadata.md) 产出,合并路径推导字段
947
- * (来自 [scanAgents](../agents/scanAgents.md) 的 `AgentManifest`)与 AST 提取字段
948
- * (JSDoc 描述、`@agent` 覆盖名、config 块字段)。
949
- *
950
- * 字段来源:
951
- * - `name` — `@agent` JSDoc 覆盖值,或 `pathMeta.name`(目录推导)
952
- * - `filePath` / `hasRun` — 由 `pathMeta` 透传
953
- * - `description` — JSDoc 注释块自由文本(对 LLM 可见)
954
- * - `systemPrompt` / `tools` / `agents` / `model` / `maxTurns` — config 块字面量提取
955
- */
956
- interface AgentMetadata extends AgentCore {
957
- /** 源码相对路径(从 `pathMeta` 透传),`loadAgentModule` 据此加载 `handler.js` 提取 `run` */
958
- filePath: string;
959
- /** 是否导出 `run` 函数(从 `pathMeta` 透传),`Agent.executeSubAgent` 据此选择自定义 run / 默认 reactLoop */
960
- hasRun: boolean;
961
- }
962
- /**
963
- * 路径推导的 agent 元数据(由 [scanAgents](../agents/scanAgents.ts) 计算)
964
- *
965
- * 透传到 [AgentMetadata](./extractAgentMetadata.ts) 输出,与 AST 提取字段合并。
966
- * 与 [ToolPathMeta](./extractToolMetadata.md) 对称。
967
- */
968
- interface AgentPathMeta {
969
- /** 目录推导的 agent 名(如 `researcher`) */
970
- name: string;
971
- /** 源码相对路径(如 `src/agents/researcher/handler.ts`) */
972
- filePath: string;
973
- /** 是否导出 `run` 函数(scanAgents 正则检测) */
974
- hasRun: boolean;
975
- }
976
-
977
- /**
978
- * Tool 的 LLM 可见核心字段
979
- *
980
- * 描述"tool 是什么"——LLM 真正需要消费的字段(发往 LLM 的 tool 定义只含
981
- * `name` / `description` / input schema),**不含**代码本体加载细节
982
- * (`filePath` / `functionName` / `inputTypeName`)。
983
- *
984
- * 与 [AgentCore](./extractAgentMetadata.md) 对称——LLM-facing 字段与代码加载
985
- * 细节分离,便于未来扩展(如 DB-driven tool 只实现 `ToolCore` 即可)。
986
- *
987
- * `toolRegistry` 查询入口 / `@faapi/agent` 子包的 `buildToolDefinitions`
988
- * 都消费 `ToolCore` 字段组装 LLM tool 列表。
989
- */
990
- interface ToolCore {
991
- /** tool 名(`@tool` JSDoc 覆盖值 或 路径推导值) */
992
- name: string;
993
- /** JSDoc 描述(tool 描述,对 LLM 可见),无 JSDoc 或 JSDoc 无自由文本时为 `undefined` */
994
- description?: string;
995
- }
996
- /**
997
- * Tool 完整元数据(文件型 tool)
998
- *
999
- * 继承 [ToolCore](./extractToolMetadata.md) 的 LLM 字段,额外扩展**代码本体加载细节**:
1000
- * - `filePath` — `loadToolModule` 加载 `handler.js` 产物定位函数用
1001
- * - `functionName` — 源码导出函数名(不受 `@tool` 覆盖影响,AST 定位 + 运行时 resolveExport 用)
1002
- * - `inputTypeName` — 第一个参数的 TypeReference 名(供 [extractTypeInfo](./extractHandlerTypes.md)
1003
- * 生成 zod schema;运行时 `resolveToolSchema` 据此定位 `zod.js`)
1004
- *
1005
- * 由 [extractToolMetadata](./extractToolMetadata.md) 产出,合并路径推导字段
1006
- * (来自 [scanTools](../tools/scanTools.md) 的 `ToolManifest`)与 AST 提取字段
1007
- * (JSDoc 描述、`@tool` 覆盖名、第一个参数 interface 名)。
1008
- *
1009
- * 字段来源:
1010
- * - `name` — `@tool` JSDoc 覆盖值,或 `pathMeta.name`(路径推导)
1011
- * - `description` — JSDoc 注释块自由文本(对 LLM 可见)
1012
- * - `filePath` / `functionName` — 由 `pathMeta` 透传
1013
- * - `inputTypeName` — 第一个参数的 TypeReference 名(供 [extractTypeInfo](./extractHandlerTypes.md) 生成 zod schema)
1014
- */
1015
- interface ToolMetadata extends ToolCore {
1016
- /** 第一个参数的 interface/type 名(用于生成 zod schema),
1017
- * 无参数/参数无类型标注/参数为内联类型字面量时为 `undefined` */
1018
- inputTypeName?: string;
1019
- /** 源码相对路径(从 `pathMeta` 透传) */
1020
- filePath: string;
1021
- /** 源码中的导出函数名(从 `pathMeta` 透传,AST 定位用,不受 `@tool` 覆盖影响) */
1022
- functionName: string;
1023
- }
1024
- /**
1025
- * 路径推导的 tool 元数据(由 [scanTools](../tools/scanTools.ts) 计算)
1026
- *
1027
- * 透传到 [ToolMetadata](./extractToolMetadata.ts) 输出,与 AST 提取字段合并。
1028
- */
1029
- interface ToolPathMeta {
1030
- /** 路径推导的 tool 名(如 `weather.getWeather`) */
1031
- name: string;
1032
- /** 源码相对路径(如 `src/tools/weather/handler.ts`) */
1033
- filePath: string;
1034
- }
1035
-
1036
- /**
1037
- * 按名查找单个 agent 的 LLM 可见元数据
1038
- *
1039
- * 返回 [AgentCore](../ast/extractAgentMetadata.md) 字段(name / description /
1040
- * systemPrompt / tools / agents / model / maxTurns),**不含** `filePath` / `hasRun`。
1041
- *
1042
- * 仅查文件 registry(编译期 `faapi-agents.js` 产物来源)。**不 fallback 到
1043
- * [skillRegistry](./skillRegistry.ts)**——skill 与 agent 职责正交不耦合,
1044
- * skill 由业务方 plugin 内部使用,不参与 agent 查询链路。
1045
- *
1046
- * 用于 LLM-facing 场景(`agents` 参数注入、`asTool` 描述、
1047
- * `resolveAgentTools` / `resolveSubAgents` 解析)。
1048
- * 加载 handler.js 执行 `run` 函数请用 [getAgentEntry](#getAgentEntry)。
1049
- *
1050
- * @param name agent 名(如 `researcher`,含 `@agent` 覆盖值)
1051
- * @returns `AgentCore` 或 `undefined`(未注册)
1052
- */
1053
- declare function getAgent(name: string): AgentCore | undefined;
1054
- /**
1055
- * 按名查找单个 agent 的完整元数据(含代码加载细节)
1056
- *
1057
- * 返回 [AgentMetadata](../ast/extractAgentMetadata.md) —— 继承 AgentCore
1058
- * 额外含 `filePath` / `hasRun`,供 `@faapi/agent` 子包 `loadAgentModule`
1059
- * 加载 handler.js 执行自定义 `run` 函数。
1060
- *
1061
- * 调用方需先判断 `entry?.hasRun` 再决定是否加载 handler.js。
1062
- *
1063
- * @param name agent 名
1064
- * @returns `AgentMetadata` 或 `undefined`(未注册)
1065
- */
1066
- declare function getAgentEntry(name: string): AgentMetadata | undefined;
1067
- /**
1068
- * agent 包装为 tool 的描述符
1069
- *
1070
- * 与 [ToolMetadata](../ast/extractToolMetadata.md) 平行结构,供 reactLoop 把
1071
- * agent 当作 tool 发给 LLM。reactLoop 按 `kind` 字段路由执行:
1072
- * - `'tool'` → `loadToolModule` 加载 handler 函数
1073
- * - `'agent'` → `loadAgentModule` 加载 agent handler + 递归 reactLoop
1074
- *
1075
- * `name` 加 `agent.` 前缀避免与常规 tool 冲突,reactLoop 据此识别 sub-agent 递归。
1076
- * 不含 input schema——agent `run` 函数参数为开放式(任意 JSON),无类型约束。
1077
- *
1078
- * `metadata` 为 [AgentCore](../ast/extractAgentMetadata.md) 类型——reactLoop 只消费
1079
- * LLM-facing 字段(systemPrompt / model / maxTurns);加载 handler.js 执行 `run`
1080
- * 函数由 `@faapi/agent` 子包通过 [getAgentEntry](#getAgentEntry) 单独获取。
1081
- */
1082
- interface AgentToolDescriptor {
1083
- /** 标识此 tool 实际是 agent(reactLoop 据此走 sub-agent 递归) */
1084
- kind: 'agent';
1085
- /** tool 名(默认 `agent.<agentName>`,避免与常规 tool 冲突) */
1086
- name: string;
1087
- /** agent 名(不含前缀,用于按名查找 agent 元数据) */
1088
- agentName: string;
1089
- /** 描述(对 LLM 可见,来自 `agent.description`),无 JSDoc 描述时为 `undefined` */
1090
- description?: string;
1091
- /** agent LLM 可见元数据引用(reactLoop 取 `systemPrompt` / `model` / `maxTurns`) */
1092
- metadata: AgentCore;
1093
- }
1094
- /**
1095
- * 解析 agent 可用 tool 集合
1096
- *
1097
- * 只返回 agent 显式声明的 tool(agent config 块的 `tools` 字段)。
1098
- * `resolveAgentTools` 只关心 agent 自身显式声明的部分,职责单一。
1099
- * sub-agent 的合并由 `@faapi/agent` 的 `Agent.buildToolDefinitions` 在更上层完成(按 `name` 去重)。
1100
- *
1101
- * agent 必须显式声明用哪些 tool,显式优于隐式。
1102
- *
1103
- * 通过 [getAgent](#getAgent) 查询文件型 agent,不 fallback 到 skillRegistry。
1104
- *
1105
- * `tools` 中未在 toolRegistry 找到的 tool 名静默跳过(tool 可选可用,不强制存在)。
1106
- *
1107
- * 跨注册表依赖 [toolRegistry](./toolRegistry.ts) 的 `getTool`,
1108
- * 两个注册表由 `createAppBase` 在同一启动阶段水合。
1109
- *
1110
- * @param name agent 名
1111
- * @returns `ToolMetadata[]`(agent 未注册返回空数组)
1112
- */
1113
- declare function resolveAgentTools(name: string): ToolMetadata[];
1114
- /**
1115
- * 解析 agent 可调用的子 agent 集合
1116
- *
1117
- * 读 `agent.agents` 字段([extractAgentMetadata](../ast/extractAgentMetadata.md)
1118
- * 提取的 `config.agents` 字面量列表),按名查找已注册文件型 agent。
1119
- *
1120
- * 不 fallback 到 skillRegistry——skill 不参与 sub-agent 递归,
1121
- * 父 agent 的 `agents` 列表只能引用文件型 agent 名。
1122
- *
1123
- * 返回 `AgentCore[]`(LLM-facing 字段,供 `@faapi/agent` 子包包装为
1124
- * `AgentToolDescriptor` 发给 LLM)。加载 sub-agent handler.js 执行 `run` 函数
1125
- * 由 `@faapi/agent` 子包通过 [getAgentEntry](#getAgentEntry) 单独获取。
1126
- *
1127
- * reactLoop 组装 LLM tool 列表:
1128
- * ```ts
1129
- * const tools = [
1130
- * ...resolveAgentTools(name), // 常规 tool
1131
- * ...resolveSubAgents(name).map((a) => asTool(a.name)!), // agent-as-tool
1132
- * ];
1133
- * ```
1134
- *
1135
- * @param name agent 名
1136
- * @returns `AgentCore[]`(`agents` 未设置 / agent 未注册返回空数组)
1137
- */
1138
- declare function resolveSubAgents(name: string): AgentCore[];
1139
-
1140
1038
  /**
1141
1039
  * 加载后的 agent 模块
1142
1040
  *
@@ -1259,7 +1157,31 @@ declare function getToolSchemaPath(tool: ToolMetadata, rootDir?: string): string
1259
1157
  declare function loadToolSchema(tool: ToolMetadata, rootDir?: string): Promise<ToolSchemaModule | undefined>;
1260
1158
 
1261
1159
  /**
1262
- * 按全名查找单个 tool
1160
+ * 按名查找单个 agent 的 LLM 可见元数据(默认实例)
1161
+ *
1162
+ * 返回 [AgentCore](../ast/extractAgentMetadata.md)(不含 filePath / hasRun)。
1163
+ * 加载 handler.js 执行 `run` 函数请用 [getAgentEntry](#getAgentEntry)。
1164
+ *
1165
+ * @param name agent 名(如 `researcher`,含 `@agent` 覆盖值)
1166
+ */
1167
+ declare function getAgent(name: string): AgentCore | undefined;
1168
+ /**
1169
+ * 按名查找单个 agent 的完整元数据(默认实例,含 filePath / hasRun)
1170
+ */
1171
+ declare function getAgentEntry(name: string): AgentMetadata | undefined;
1172
+ /**
1173
+ * 解析 agent 显式声明的 tool 集合(默认实例,跨查默认 tool 注册表)
1174
+ */
1175
+ declare function resolveAgentTools(name: string): ToolMetadata[];
1176
+ /**
1177
+ * 解析 agent 可调用的子 agent 集合(默认实例,按声明顺序)
1178
+ */
1179
+ declare function resolveSubAgents(name: string): AgentCore[];
1180
+
1181
+ /**
1182
+ * 按全名查找单个 tool(默认实例)
1183
+ *
1184
+ * 框架路径(`@faapi/agent` 插件 / 请求注入)从 app 实例查找。
1263
1185
  *
1264
1186
  * @param tool 全名(如 `weather.getWeather`)
1265
1187
  * @returns `ToolMetadata` 或 `undefined`(未注册)
@@ -1267,48 +1189,41 @@ declare function loadToolSchema(tool: ToolMetadata, rootDir?: string): Promise<T
1267
1189
  declare function getTool(name: string): ToolMetadata | undefined;
1268
1190
 
1269
1191
  /**
1270
- * 水合 skill 注册表(全量替换)
1192
+ * skill 注册表全局访问器(默认实例便捷入口)
1271
1193
  *
1272
- * 业务方 plugin `lifecycle.onReady` 启动期调用:全量查 DB → 转 `AgentCore[]`
1273
- * 调本函数灌入。与 `hydrateAgentRegistry` 同构,全量替换而非增量。
1194
+ * 框架推荐路径已改为 **app 实例级注册表**:业务方 plugin
1195
+ * `lifecycle.onReady(ctx)` 中通过 `ctx.registries.skill` 灌入 DB skill——
1196
+ * 这样 skill 与该 app 的生命周期绑定,多 app 同进程互不串台,且 app close
1197
+ * 时随实例销毁。
1274
1198
  *
1275
- * 运行时增量更新场景(DB change stream)用 [upsertSkill](#upsertSkill) /
1276
- * [removeSkill](#removeSkill),不走本函数。
1199
+ * 本模块的同名函数保留为**默认实例**的便捷访问器(向后兼容),但注意:
1200
+ * 默认实例与 app 实例相互独立——经全局函数灌入的 skill 不会出现在
1201
+ * 该 app 的请求链路中。
1277
1202
  *
1278
- * @param skills DB / 外部源加载并转好的 `AgentCore[]`
1203
+ * 详见 [skillRegistry.md](./skillRegistry.md) [registries.md](./registries.md)。
1279
1204
  */
1280
- declare function hydrateSkillRegistry(skills: AgentCore[]): void;
1281
1205
  /**
1282
- * 单条增改 skill(运行时增量)
1283
- *
1284
- * 监听 DB change stream 的 `insert` / `update` 事件时调用。
1285
- * `Map.set` 原子操作,并发安全(多请求同时 upsert 最后一次 wins)。
1206
+ * 水合默认实例的 skill 注册表(全量替换)
1286
1207
  *
1287
- * 同名 skill 覆盖(更新),不重复累积。
1208
+ * 框架推荐路径:`lifecycle.onReady(ctx)` 中 `ctx.registries.skill.hydrate(skills)`。
1209
+ */
1210
+ declare function hydrateSkillRegistry(skills: AgentCore[]): void;
1211
+ /**
1212
+ * 单条增改默认实例的 skill(运行时增量)
1288
1213
  *
1289
- * @param core skill 的 LLM 可见元数据
1214
+ * 框架推荐路径:`ctx.registries.skill.upsert(skill)`。
1290
1215
  */
1291
1216
  declare function upsertSkill(core: AgentCore): void;
1292
1217
  /**
1293
- * 单条删除 skill(运行时增量)
1294
- *
1295
- * 监听 DB change stream 的 `delete` 事件时调用。
1296
- * 幂等:删除不存在的 name 静默无操作,不抛错。
1297
- *
1298
- * @param name skill 名
1218
+ * 单条删除默认实例的 skill(幂等)
1299
1219
  */
1300
1220
  declare function removeSkill(name: string): void;
1301
1221
  /**
1302
- * 按名查单个 skill
1303
- *
1304
- * @param name skill 名
1305
- * @returns `AgentCore` 或 `undefined`(未注册)
1222
+ * 按名查默认实例的单个 skill
1306
1223
  */
1307
1224
  declare function getSkill(name: string): AgentCore | undefined;
1308
1225
  /**
1309
- * 返回所有已注册 skill
1310
- *
1311
- * 返回副本,调用方修改不影响内部状态(与 `listAgents` / `listTools` 同构)。
1226
+ * 返回默认实例所有已注册 skill(副本)
1312
1227
  */
1313
1228
  declare function listSkills(): AgentCore[];
1314
1229
 
@@ -1326,20 +1241,26 @@ declare function listSkills(): AgentCore[];
1326
1241
  /** agent handle 工厂函数(由 `@faapi/agent` 插件注册) */
1327
1242
  type AgentHandleFactory = (ctx: FaapiContext) => unknown;
1328
1243
  /**
1329
- * 注册 agent handle 工厂
1244
+ * agent handle 工厂全局访问器(默认实例便捷入口)
1330
1245
  *
1331
- * `@faapi/agent` 插件在 `setup()` 时调用,传入创建 `AgentHandle` 的工厂函数。
1332
- * 二次注册覆盖第一次(与 `hydrateAgentRegistry` 全量替换同构)。
1246
+ * 框架路径已改为 app 实例:`@faapi/agent` 插件在 setup 时经
1247
+ * `ctx.registries.agentHandle.register(factory)` 注册到**当前 app 的实例**,
1248
+ * 请求注入经 `ctx.registries.agentHandle.get(ctx)` 读取——多 app 同进程互不覆盖。
1249
+ * 此前模块级单值会让第二个 app 的插件注册覆盖第一个。
1333
1250
  *
1334
- * 传入 `null` 等效于 [clearAgentHandleFactory](#clearAgentHandleFactory)。
1251
+ * 本模块的同名函数保留为默认实例便捷访问器(向后兼容)。
1335
1252
  *
1336
- * @param factory 工厂函数或 `null`(清理)
1253
+ * 详见 [agentHandle.md](./agentHandle.md) [registries.md](./registries.md)。
1337
1254
  */
1338
- declare function registerAgentHandleFactory(factory: AgentHandleFactory | null): void;
1339
1255
  /**
1340
- * 清空工厂注册(app close / 测试清理时调用)
1256
+ * 注册默认实例的 agent handle 工厂
1341
1257
  *
1342
- * `clearAgentRegistry` / `clearToolRegistry` 对称,避免测试间状态泄漏。
1258
+ * 框架路径请使用 `ctx.registries.agentHandle.register(factory)`(PluginContext 提供)。
1259
+ * 传入 `null` 等效于清理。
1260
+ */
1261
+ declare function registerAgentHandleFactory(factory: AgentHandleFactory | null): void;
1262
+ /**
1263
+ * 清空默认实例的工厂注册
1343
1264
  */
1344
1265
  declare function clearAgentHandleFactory(): void;
1345
1266
 
@@ -1490,6 +1411,8 @@ interface CreateAppOptions {
1490
1411
  interface AppBase {
1491
1412
  /** Node.js Server 实例(listen 后可用,close 后置 null) */
1492
1413
  server: Server | null;
1414
+ /** app 级注册表(tool/agent/skill/agentHandle 实例,close 时清理) */
1415
+ registries: AppRegistries;
1493
1416
  /** 排序后的路由清单 */
1494
1417
  routes: RouteManifest;
1495
1418
  /** WebSocket 路由清单 */
@@ -1566,4 +1489,4 @@ type ProdApp = AppBase;
1566
1489
  */
1567
1490
  declare function createProdApp(options?: CreateAppOptions): Promise<ProdApp>;
1568
1491
 
1569
- export { type AgentConfig, type AgentCore, type AgentHandleFactory, type AgentMetadata, type AgentModule, type AgentPathMeta, type AgentToolDescriptor, type ProdApp as App, CorsOptions, type CreateAppOptions, 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, LoggerOptions, MethodNotAllowedError, ModuleLoadError, type PluginContext, type PluginDeclaration, type ProdApp, type PropertyType, type RequestHandler, type ResponseConfig, RouteManifest, RouteNotFoundError, type RouteSchemaSource, type RuntimeType, SchemaExtractionError, type ToolCore, type ToolMetadata, type ToolModule, type ToolPathMeta, type ToolSchemaModule, type TypeConstraint, type UpgradeHandler, ValidationError, type ValidationErrorCode, type ValidationIssue, type WsContext, type WsEventHandlers, type WsHandler, type WsSocket, clearAgentHandleFactory, collectRouteSchemaSources, createProdApp as createApp, createDevApp, createProdApp, createProgram, createPrograms, extractTypeInfo, getAgent, getAgentEntry, getApp, getInputTypeForMethod, getSkill, getTool, getToolSchemaPath, hydrateSkillRegistry, invalidateProgramCache, listSkills, loadAgentModule, loadConfig, loadEnv, loadToolModule, loadToolSchema, registerAgentHandleFactory, removeSkill, resolveAgentTools, resolveSubAgents, resolveTypeNode, upsertSkill };
1492
+ export { type AgentConfig, AgentCore, AgentMetadata, type AgentModule, type ProdApp as App, AppRegistries, CorsOptions, type CreateAppOptions, 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, LoggerOptions, MethodNotAllowedError, ModuleLoadError, type PluginContext, type PluginDeclaration, type ProdApp, type PropertyType, type RequestHandler, type ResponseConfig, RouteManifest, RouteNotFoundError, type RouteSchemaSource, type RuntimeType, SchemaExtractionError, ToolMetadata, type ToolModule, type ToolSchemaModule, type TypeConstraint, type UpgradeHandler, ValidationError, type ValidationErrorCode, type ValidationIssue, type WsContext, type WsEventHandlers, type WsHandler, type WsSocket, clearAgentHandleFactory, collectRouteSchemaSources, createProdApp as createApp, createDevApp, createProdApp, createProgram, createPrograms, extractTypeInfo, getAgent, getAgentEntry, getApp, getInputTypeForMethod, getSkill, getTool, getToolSchemaPath, hydrateSkillRegistry, invalidateProgramCache, listSkills, loadAgentModule, loadConfig, loadEnv, loadToolModule, loadToolSchema, registerAgentHandleFactory, removeSkill, resolveAgentTools, resolveSubAgents, resolveTypeNode, upsertSkill };