@faapi/faapi 6.30.0 → 6.32.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 +109 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/{contextTypes-DhagvRH_.d.ts → contextTypes-WKsf3cCE.d.ts} +1149 -50
- package/dist/index.d.ts +32 -774
- package/dist/index.js +116 -10
- package/dist/index.js.map +1 -1
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +22 -1
- package/dist/testing.js.map +1 -1
- package/dist/workerEntry.d.ts +17 -2
- package/dist/workerEntry.js +25 -0
- package/dist/workerEntry.js.map +1 -1
- package/package.json +1 -1
- package/dist/routeTypes-0rwuIws-.d.ts +0 -204
package/dist/workerEntry.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { E as LlmConfig, B as LlmComplete, ad as TaskRegistriesSnapshot, e as AgentMetadata, f as ToolMetadata, A as AgentCore, K as LogLevel } from './contextTypes-WKsf3cCE.js';
|
|
2
|
+
import 'node:http';
|
|
3
|
+
import 'node:net';
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* 任务隔离 worker 的真实入口文件(tsup 独立入口 → dist/workerEntry.js)
|
|
@@ -69,6 +71,19 @@ declare function buildRegistriesView(snapshot?: TaskRegistriesSnapshot): {
|
|
|
69
71
|
* 属性丢失、stack 重新生成),不序列化就无法跨线程保真。
|
|
70
72
|
*/
|
|
71
73
|
declare function serializeError(err: unknown): SerializedWorkerError;
|
|
74
|
+
/**
|
|
75
|
+
* worker 内重建轻量 LLM 补全通道(隔离任务路径)
|
|
76
|
+
*
|
|
77
|
+
* channel 是函数闭包不可跨线程;`agent.llms` 是纯数据——postMessage 传入后在本
|
|
78
|
+
* 函数内动态加载 `@faapi/agent` 重建实例(与 task.ts 内 import '@faapi/agent'
|
|
79
|
+
* 同一解析来源,业务方安装即可用)。specifier 变量拼接避免主包静态依赖
|
|
80
|
+
* `@faapi/agent`(与 loadTaskDriver 加载驱动子包同策略)。
|
|
81
|
+
*
|
|
82
|
+
* 降级路径(已记入 fallback.md):`@faapi/agent` 未安装/不可解析时 `console.warn`
|
|
83
|
+
* 留痕并返回 `undefined`——taskCtx.llm 为 undefined,任务自身决定报错或旁路;
|
|
84
|
+
* llms 未配置时不触发加载。
|
|
85
|
+
*/
|
|
86
|
+
declare function buildLlmChannel(llms?: Record<string, LlmConfig>): Promise<LlmComplete | undefined>;
|
|
72
87
|
/**
|
|
73
88
|
* 任务日志器:级别预过滤(level 为 undefined 时不过滤——宿主文件管道默认全量)+
|
|
74
89
|
* scope/fields 组装,条目作为纯数据经 `{ type: 'log' }` 消息回传宿主、由宿主统一
|
|
@@ -87,4 +102,4 @@ declare function createTaskLogger(level?: LogLevel, scope?: string, fields?: Rec
|
|
|
87
102
|
child: (cs: string) => /*elided*/ any;
|
|
88
103
|
};
|
|
89
104
|
|
|
90
|
-
export { type SerializedWorkerError, type WorkerEntryData, buildRegistriesView, createTaskLogger, serializeError };
|
|
105
|
+
export { type SerializedWorkerError, type WorkerEntryData, buildLlmChannel, buildRegistriesView, createTaskLogger, serializeError };
|
package/dist/workerEntry.js
CHANGED
|
@@ -65,6 +65,28 @@ function serializeError(err) {
|
|
|
65
65
|
return { name: "Error", message: String(err), stack: void 0, props: {} };
|
|
66
66
|
}
|
|
67
67
|
var LOG_RANK = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
68
|
+
async function buildLlmChannel(llms) {
|
|
69
|
+
if (!llms || Object.keys(llms).length === 0) return void 0;
|
|
70
|
+
const specifier = "@faapi/agent";
|
|
71
|
+
try {
|
|
72
|
+
const mod = await import(
|
|
73
|
+
/* @vite-ignore */
|
|
74
|
+
specifier
|
|
75
|
+
);
|
|
76
|
+
if (typeof mod.createLightComplete !== "function") {
|
|
77
|
+
console.warn(
|
|
78
|
+
"[faapi] @faapi/agent resolved but does not export createLightComplete() \u2014 taskCtx.llm unavailable (check package version)"
|
|
79
|
+
);
|
|
80
|
+
return void 0;
|
|
81
|
+
}
|
|
82
|
+
return mod.createLightComplete({ llms });
|
|
83
|
+
} catch {
|
|
84
|
+
console.warn(
|
|
85
|
+
"[faapi] agent.llms is configured but @faapi/agent is not resolvable in the task worker \u2014 install @faapi/agent to enable taskCtx.llm (task execution continues)"
|
|
86
|
+
);
|
|
87
|
+
return void 0;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
68
90
|
function createTaskLogger(level, scope, fields) {
|
|
69
91
|
const make = (suffix) => {
|
|
70
92
|
const write = (lvl, message, callFields) => {
|
|
@@ -130,10 +152,12 @@ function bootstrap() {
|
|
|
130
152
|
const registries = buildRegistriesView(msg.registries);
|
|
131
153
|
const progress = (value) => parentPort?.postMessage({ type: "progress", value });
|
|
132
154
|
const taskLog = msg.log ? createTaskLogger(msg.log.level, msg.log.scope, msg.log.fields) : void 0;
|
|
155
|
+
const llm = await buildLlmChannel(msg.llms);
|
|
133
156
|
const result = await run(payload, {
|
|
134
157
|
...taskCtx,
|
|
135
158
|
signal: controller.signal,
|
|
136
159
|
registries,
|
|
160
|
+
llm,
|
|
137
161
|
progress,
|
|
138
162
|
log: taskLog
|
|
139
163
|
});
|
|
@@ -160,6 +184,7 @@ function bootstrap() {
|
|
|
160
184
|
}
|
|
161
185
|
bootstrap();
|
|
162
186
|
export {
|
|
187
|
+
buildLlmChannel,
|
|
163
188
|
buildRegistriesView,
|
|
164
189
|
createTaskLogger,
|
|
165
190
|
serializeError
|
package/dist/workerEntry.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/task/workerEntry.ts"],"sourcesContent":["import { parentPort, workerData } from 'node:worker_threads';\nimport type { TaskRegistriesSnapshot } from './taskTypes';\nimport type { LogEntry, LogLevel } from '../logger/loggerTypes';\n\n/**\n * 任务隔离 worker 的真实入口文件(tsup 独立入口 → dist/workerEntry.js)\n *\n * 为什么是独立文件而不是宿主生成的 data URL:data URL 模块无法解析裸说明符\n * ('@faapi/faapi'),framework 侧 helper 只能以字符串模板内联(typecheck 覆盖\n * 不到、需对照测试锚定漂移),且求值顺序需人肉控制;真实入口文件是隔离执行的\n * 通行形态(vitest / bullmq / piscina 同款)——入口自身的语句先于对用户任务\n * 模块的动态 import 执行,\"读取根播种先于任务模块求值\"由结构保证。\n *\n * 本文件自包含:值导入仅 node:worker_threads,其余全部 type-only(类型剥离后\n * 擦除)——vitest 场景宿主会回退到 src 源文件直跑(resolveWorkerEntryUrl,见\n * taskWorker.ts),无相对导入链才无扩展名解析问题。读取根经 globalThis +\n * Symbol.for 承载而非模块状态:入口 bundle 与 index bundle 是两份代码副本,\n * globalThis 是唯一跨副本共享面(symbol key 与 utils/readResource 的\n * 'faapi.resources.dir' 一致,两处字面量需同步,readResource.test.ts 契约测试\n * 锚定)。主线程 import 本模块仅取导出的纯函数(bootstrap 被 parentPort 守卫),\n * 真实 worker 由 taskWorker.ts 创建。\n */\n\n/** workerData 契约(taskWorker.ts 构造 worker 时传入) */\nexport interface WorkerEntryData {\n /** 任务产物模块 file:// URL(首次 run 消息时动态 import) */\n moduleUrl: string;\n /** 产物 resources 目录绝对路径(缺省不播种——readResource 未绑定即显式抛错) */\n resourcesDir?: string;\n}\n\n/**\n * sub-agent 派发工具名前缀(与 injection/subAgentToolName.ts 的 SUB_AGENT_TOOL_PREFIX\n * 同步——本文件自包含不可值导入,taskWorker.test.ts 断言完整派发名锚定漂移)。\n * 快照内的 agent 名来自文件注册表(scanAgents 构建期已校验字符集),直拼即合法。\n */\nconst SUB_AGENT_TOOL_PREFIX = 'agent-';\n\n/** worker 错误回传负载(serializeError 的产物,宿主 reviveError 重建) */\nexport interface SerializedWorkerError {\n name: string;\n message: string;\n stack: string | undefined;\n props: Record<string, unknown>;\n}\n\n/**\n * worker 内重建注册表只读视图(语义与宿主 createTaskRegistriesView 一致:\n * 不存在的 tool/agent 名静默跳过、resolveAgentTools 按解析后 name 去重)\n *\n * 快照为纯数据(函数闭包不可跨线程),视图在 worker 内重建。\n */\nexport function buildRegistriesView(snapshot?: TaskRegistriesSnapshot) {\n const agents = new Map((snapshot?.agents ?? []).map((a) => [a.name, a] as const));\n const tools = new Map((snapshot?.tools ?? []).map((t) => [t.name, t] as const));\n const skills = new Map((snapshot?.skills ?? []).map((s) => [s.name, s] as const));\n return {\n agent: {\n getAgent: (name: string) => agents.get(name),\n getAgentEntry: (name: string) => agents.get(name),\n listAgents: () => Array.from(agents.values()),\n asTool: (name: string) => {\n const agent = agents.get(name);\n if (!agent) return undefined;\n return {\n kind: 'agent' as const,\n name: `${SUB_AGENT_TOOL_PREFIX}${agent.name}`,\n agentName: agent.name,\n description: agent.description,\n metadata: agent,\n };\n },\n resolveAgentTools: (name: string) => {\n const agent = agents.get(name);\n const result = new Map<string, TaskRegistriesSnapshot['tools'][number]>();\n if (agent?.tools) {\n for (const toolName of agent.tools) {\n const resolved = tools.get(toolName);\n if (resolved) result.set(resolved.name, resolved);\n }\n }\n return Array.from(result.values());\n },\n resolveSubAgents: (name: string) => {\n const agent = agents.get(name);\n const result: TaskRegistriesSnapshot['agents'] = [];\n if (agent?.agents) {\n for (const subName of agent.agents) {\n const sub = agents.get(subName);\n if (sub) result.push(sub);\n }\n }\n return result;\n },\n },\n tool: {\n get: (name: string) => tools.get(name),\n list: () => Array.from(tools.values()),\n },\n skill: {\n get: (name: string) => skills.get(name),\n list: () => Array.from(skills.values()),\n },\n };\n}\n\n/**\n * worker 内错误序列化(错误保真):Error 按 `{ name, message, stack, props }` 回传——\n * `props` 收集自定义可枚举属性(业务错误类的 code/statusCode 等),宿主侧 reviveError\n * 重建时回填;非 Error 值按 String(err) 归一。结构化克隆只保 message(name/自定义\n * 属性丢失、stack 重新生成),不序列化就无法跨线程保真。\n */\nexport function serializeError(err: unknown): SerializedWorkerError {\n if (err instanceof Error) {\n const props: Record<string, unknown> = {};\n for (const key of Object.keys(err)) {\n props[key] = (err as unknown as Record<string, unknown>)[key];\n }\n return { name: err.name, message: err.message, stack: err.stack, props };\n }\n return { name: 'Error', message: String(err), stack: undefined, props: {} };\n}\n\nconst LOG_RANK: Record<LogLevel, number> = { debug: 0, info: 1, warn: 2, error: 3 };\n\n/**\n * 任务日志器:级别预过滤(level 为 undefined 时不过滤——宿主文件管道默认全量)+\n * scope/fields 组装,条目作为纯数据经 `{ type: 'log' }` 消息回传宿主、由宿主统一\n * sink 输出(语义与主包 createLogger 对齐:child scope `:` 合并、调用处 fields\n * 覆盖构造字段;格式化在宿主侧,不跨线程复制格式代码)。\n *\n * fields 含不可克隆值时丢弃 fields、保底 level/message/scope(降级已记入\n * fallback.md)——日志永不中断任务执行,与 progress 的\"不可克隆按执行错误处理\"\n * 不同。\n */\nexport function createTaskLogger(\n level?: LogLevel,\n scope?: string,\n fields?: Record<string, unknown>,\n) {\n const make = (suffix?: string) => {\n const write = (lvl: LogLevel, message: string, callFields?: Record<string, unknown>) => {\n if (level !== undefined && LOG_RANK[lvl] < LOG_RANK[level]) return;\n const entry: LogEntry = { level: lvl, message, time: new Date().toISOString() };\n const fullScope =\n suffix === undefined ? scope : scope === undefined ? suffix : `${scope}:${suffix}`;\n if (fullScope !== undefined) entry.scope = fullScope;\n if (fields !== undefined || callFields !== undefined) {\n entry.fields = { ...fields, ...callFields };\n }\n try {\n parentPort?.postMessage({ type: 'log', entry });\n } catch {\n entry.fields = { warning: 'log fields not cloneable across worker boundary, dropped' };\n parentPort?.postMessage({ type: 'log', entry });\n }\n };\n return {\n debug: (m: string, f?: Record<string, unknown>) => write('debug', m, f),\n info: (m: string, f?: Record<string, unknown>) => write('info', m, f),\n warn: (m: string, f?: Record<string, unknown>) => write('warn', m, f),\n error: (m: string, f?: Record<string, unknown>) => write('error', m, f),\n child: (cs: string) => make(suffix === undefined ? cs : `${suffix}:${cs}`),\n };\n };\n return make(undefined);\n}\n\n/**\n * worker 侧 bootstrap:播种读取根 + 桥接 run 消息\n *\n * 仅在 worker 线程执行(主线程 import 本模块时 parentPort 为空,只取上面的纯\n * 函数)。任务模块在首次 run 消息时才动态 import(modulePromise 缓存)——播种\n * 语句先于它执行,任务模块顶层代码(top-level await 调用 readResource)读到\n * 的绑定必然已就位。\n */\nfunction bootstrap(): void {\n if (!parentPort) return;\n const data = workerData as WorkerEntryData;\n\n // 播种全局 readResource 读取根(缺省不播种——直接构造队列的测试/嵌入场景,\n // readResource 未绑定即显式抛错)\n if (data.resourcesDir) {\n (globalThis as Record<symbol, string | undefined>)[Symbol.for('faapi.resources.dir')] =\n data.resourcesDir;\n }\n\n let controller: AbortController | null = null;\n let modulePromise: Promise<{ run?: (payload: unknown, taskCtx: unknown) => unknown }> | null =\n null;\n\n parentPort.on(\n 'message',\n (msg: {\n type?: string;\n reason?: string;\n payload?: unknown;\n taskCtx?: Record<string, unknown>;\n registries?: TaskRegistriesSnapshot;\n log?: { level?: LogLevel; scope?: string; fields?: Record<string, unknown> };\n }) => {\n if (msg?.type === 'abort') {\n controller?.abort(new Error(msg.reason));\n return;\n }\n if (msg?.type !== 'run') return;\n controller = new AbortController();\n const { payload, taskCtx } = msg;\n void (async () => {\n try {\n modulePromise ??= import(data.moduleUrl);\n const mod = await modulePromise;\n const run = mod.run;\n if (typeof run !== 'function') {\n parentPort?.postMessage({\n type: 'error',\n error: {\n name: 'Error',\n message: 'Task module has no run export',\n stack: undefined,\n props: {},\n },\n });\n return;\n }\n const registries = buildRegistriesView(msg.registries);\n const progress = (value: unknown) => parentPort?.postMessage({ type: 'progress', value });\n const taskLog = msg.log\n ? createTaskLogger(msg.log.level, msg.log.scope, msg.log.fields)\n : undefined;\n const result = await run(payload, {\n ...taskCtx,\n signal: controller!.signal,\n registries,\n progress,\n log: taskLog,\n });\n parentPort?.postMessage({ type: 'done', result });\n } catch (err) {\n const serialized = serializeError(err);\n try {\n parentPort?.postMessage({ type: 'error', error: serialized });\n } catch {\n // props 含不可克隆值(函数等)时丢弃 props,保底 name/message/stack\n parentPort?.postMessage({\n type: 'error',\n error: {\n name: serialized.name,\n message: serialized.message,\n stack: serialized.stack,\n props: {},\n },\n });\n }\n }\n })();\n },\n );\n}\n\nbootstrap();\n"],"mappings":";AAAA,SAAS,YAAY,kBAAkB;AAoCvC,IAAM,wBAAwB;AAgBvB,SAAS,oBAAoB,UAAmC;AACrE,QAAM,SAAS,IAAI,KAAK,UAAU,UAAU,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAU,CAAC;AAChF,QAAM,QAAQ,IAAI,KAAK,UAAU,SAAS,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAU,CAAC;AAC9E,QAAM,SAAS,IAAI,KAAK,UAAU,UAAU,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAU,CAAC;AAChF,SAAO;AAAA,IACL,OAAO;AAAA,MACL,UAAU,CAAC,SAAiB,OAAO,IAAI,IAAI;AAAA,MAC3C,eAAe,CAAC,SAAiB,OAAO,IAAI,IAAI;AAAA,MAChD,YAAY,MAAM,MAAM,KAAK,OAAO,OAAO,CAAC;AAAA,MAC5C,QAAQ,CAAC,SAAiB;AACxB,cAAM,QAAQ,OAAO,IAAI,IAAI;AAC7B,YAAI,CAAC,MAAO,QAAO;AACnB,eAAO;AAAA,UACL,MAAM;AAAA,UACN,MAAM,GAAG,qBAAqB,GAAG,MAAM,IAAI;AAAA,UAC3C,WAAW,MAAM;AAAA,UACjB,aAAa,MAAM;AAAA,UACnB,UAAU;AAAA,QACZ;AAAA,MACF;AAAA,MACA,mBAAmB,CAAC,SAAiB;AACnC,cAAM,QAAQ,OAAO,IAAI,IAAI;AAC7B,cAAM,SAAS,oBAAI,IAAqD;AACxE,YAAI,OAAO,OAAO;AAChB,qBAAW,YAAY,MAAM,OAAO;AAClC,kBAAM,WAAW,MAAM,IAAI,QAAQ;AACnC,gBAAI,SAAU,QAAO,IAAI,SAAS,MAAM,QAAQ;AAAA,UAClD;AAAA,QACF;AACA,eAAO,MAAM,KAAK,OAAO,OAAO,CAAC;AAAA,MACnC;AAAA,MACA,kBAAkB,CAAC,SAAiB;AAClC,cAAM,QAAQ,OAAO,IAAI,IAAI;AAC7B,cAAM,SAA2C,CAAC;AAClD,YAAI,OAAO,QAAQ;AACjB,qBAAW,WAAW,MAAM,QAAQ;AAClC,kBAAM,MAAM,OAAO,IAAI,OAAO;AAC9B,gBAAI,IAAK,QAAO,KAAK,GAAG;AAAA,UAC1B;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,MAAM;AAAA,MACJ,KAAK,CAAC,SAAiB,MAAM,IAAI,IAAI;AAAA,MACrC,MAAM,MAAM,MAAM,KAAK,MAAM,OAAO,CAAC;AAAA,IACvC;AAAA,IACA,OAAO;AAAA,MACL,KAAK,CAAC,SAAiB,OAAO,IAAI,IAAI;AAAA,MACtC,MAAM,MAAM,MAAM,KAAK,OAAO,OAAO,CAAC;AAAA,IACxC;AAAA,EACF;AACF;AAQO,SAAS,eAAe,KAAqC;AAClE,MAAI,eAAe,OAAO;AACxB,UAAM,QAAiC,CAAC;AACxC,eAAW,OAAO,OAAO,KAAK,GAAG,GAAG;AAClC,YAAM,GAAG,IAAK,IAA2C,GAAG;AAAA,IAC9D;AACA,WAAO,EAAE,MAAM,IAAI,MAAM,SAAS,IAAI,SAAS,OAAO,IAAI,OAAO,MAAM;AAAA,EACzE;AACA,SAAO,EAAE,MAAM,SAAS,SAAS,OAAO,GAAG,GAAG,OAAO,QAAW,OAAO,CAAC,EAAE;AAC5E;AAEA,IAAM,WAAqC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE;AAY3E,SAAS,iBACd,OACA,OACA,QACA;AACA,QAAM,OAAO,CAAC,WAAoB;AAChC,UAAM,QAAQ,CAAC,KAAe,SAAiB,eAAyC;AACtF,UAAI,UAAU,UAAa,SAAS,GAAG,IAAI,SAAS,KAAK,EAAG;AAC5D,YAAM,QAAkB,EAAE,OAAO,KAAK,SAAS,OAAM,oBAAI,KAAK,GAAE,YAAY,EAAE;AAC9E,YAAM,YACJ,WAAW,SAAY,QAAQ,UAAU,SAAY,SAAS,GAAG,KAAK,IAAI,MAAM;AAClF,UAAI,cAAc,OAAW,OAAM,QAAQ;AAC3C,UAAI,WAAW,UAAa,eAAe,QAAW;AACpD,cAAM,SAAS,EAAE,GAAG,QAAQ,GAAG,WAAW;AAAA,MAC5C;AACA,UAAI;AACF,oBAAY,YAAY,EAAE,MAAM,OAAO,MAAM,CAAC;AAAA,MAChD,QAAQ;AACN,cAAM,SAAS,EAAE,SAAS,2DAA2D;AACrF,oBAAY,YAAY,EAAE,MAAM,OAAO,MAAM,CAAC;AAAA,MAChD;AAAA,IACF;AACA,WAAO;AAAA,MACL,OAAO,CAAC,GAAW,MAAgC,MAAM,SAAS,GAAG,CAAC;AAAA,MACtE,MAAM,CAAC,GAAW,MAAgC,MAAM,QAAQ,GAAG,CAAC;AAAA,MACpE,MAAM,CAAC,GAAW,MAAgC,MAAM,QAAQ,GAAG,CAAC;AAAA,MACpE,OAAO,CAAC,GAAW,MAAgC,MAAM,SAAS,GAAG,CAAC;AAAA,MACtE,OAAO,CAAC,OAAe,KAAK,WAAW,SAAY,KAAK,GAAG,MAAM,IAAI,EAAE,EAAE;AAAA,IAC3E;AAAA,EACF;AACA,SAAO,KAAK,MAAS;AACvB;AAUA,SAAS,YAAkB;AACzB,MAAI,CAAC,WAAY;AACjB,QAAM,OAAO;AAIb,MAAI,KAAK,cAAc;AACrB,IAAC,WAAkD,uBAAO,IAAI,qBAAqB,CAAC,IAClF,KAAK;AAAA,EACT;AAEA,MAAI,aAAqC;AACzC,MAAI,gBACF;AAEF,aAAW;AAAA,IACT;AAAA,IACA,CAAC,QAOK;AACJ,UAAI,KAAK,SAAS,SAAS;AACzB,oBAAY,MAAM,IAAI,MAAM,IAAI,MAAM,CAAC;AACvC;AAAA,MACF;AACA,UAAI,KAAK,SAAS,MAAO;AACzB,mBAAa,IAAI,gBAAgB;AACjC,YAAM,EAAE,SAAS,QAAQ,IAAI;AAC7B,YAAM,YAAY;AAChB,YAAI;AACF,4BAAkB,OAAO,KAAK;AAC9B,gBAAM,MAAM,MAAM;AAClB,gBAAM,MAAM,IAAI;AAChB,cAAI,OAAO,QAAQ,YAAY;AAC7B,wBAAY,YAAY;AAAA,cACtB,MAAM;AAAA,cACN,OAAO;AAAA,gBACL,MAAM;AAAA,gBACN,SAAS;AAAA,gBACT,OAAO;AAAA,gBACP,OAAO,CAAC;AAAA,cACV;AAAA,YACF,CAAC;AACD;AAAA,UACF;AACA,gBAAM,aAAa,oBAAoB,IAAI,UAAU;AACrD,gBAAM,WAAW,CAAC,UAAmB,YAAY,YAAY,EAAE,MAAM,YAAY,MAAM,CAAC;AACxF,gBAAM,UAAU,IAAI,MAChB,iBAAiB,IAAI,IAAI,OAAO,IAAI,IAAI,OAAO,IAAI,IAAI,MAAM,IAC7D;AACJ,gBAAM,SAAS,MAAM,IAAI,SAAS;AAAA,YAChC,GAAG;AAAA,YACH,QAAQ,WAAY;AAAA,YACpB;AAAA,YACA;AAAA,YACA,KAAK;AAAA,UACP,CAAC;AACD,sBAAY,YAAY,EAAE,MAAM,QAAQ,OAAO,CAAC;AAAA,QAClD,SAAS,KAAK;AACZ,gBAAM,aAAa,eAAe,GAAG;AACrC,cAAI;AACF,wBAAY,YAAY,EAAE,MAAM,SAAS,OAAO,WAAW,CAAC;AAAA,UAC9D,QAAQ;AAEN,wBAAY,YAAY;AAAA,cACtB,MAAM;AAAA,cACN,OAAO;AAAA,gBACL,MAAM,WAAW;AAAA,gBACjB,SAAS,WAAW;AAAA,gBACpB,OAAO,WAAW;AAAA,gBAClB,OAAO,CAAC;AAAA,cACV;AAAA,YACF,CAAC;AAAA,UACH;AAAA,QACF;AAAA,MACF,GAAG;AAAA,IACL;AAAA,EACF;AACF;AAEA,UAAU;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/task/workerEntry.ts"],"sourcesContent":["import { parentPort, workerData } from 'node:worker_threads';\nimport type { TaskRegistriesSnapshot } from './taskTypes';\nimport type { LlmComplete } from '../injection/llmTypes';\nimport type { LlmConfig } from '../config/configTypes';\nimport type { LogEntry, LogLevel } from '../logger/loggerTypes';\n\n/**\n * 任务隔离 worker 的真实入口文件(tsup 独立入口 → dist/workerEntry.js)\n *\n * 为什么是独立文件而不是宿主生成的 data URL:data URL 模块无法解析裸说明符\n * ('@faapi/faapi'),framework 侧 helper 只能以字符串模板内联(typecheck 覆盖\n * 不到、需对照测试锚定漂移),且求值顺序需人肉控制;真实入口文件是隔离执行的\n * 通行形态(vitest / bullmq / piscina 同款)——入口自身的语句先于对用户任务\n * 模块的动态 import 执行,\"读取根播种先于任务模块求值\"由结构保证。\n *\n * 本文件自包含:值导入仅 node:worker_threads,其余全部 type-only(类型剥离后\n * 擦除)——vitest 场景宿主会回退到 src 源文件直跑(resolveWorkerEntryUrl,见\n * taskWorker.ts),无相对导入链才无扩展名解析问题。读取根经 globalThis +\n * Symbol.for 承载而非模块状态:入口 bundle 与 index bundle 是两份代码副本,\n * globalThis 是唯一跨副本共享面(symbol key 与 utils/readResource 的\n * 'faapi.resources.dir' 一致,两处字面量需同步,readResource.test.ts 契约测试\n * 锚定)。主线程 import 本模块仅取导出的纯函数(bootstrap 被 parentPort 守卫),\n * 真实 worker 由 taskWorker.ts 创建。\n */\n\n/** workerData 契约(taskWorker.ts 构造 worker 时传入) */\nexport interface WorkerEntryData {\n /** 任务产物模块 file:// URL(首次 run 消息时动态 import) */\n moduleUrl: string;\n /** 产物 resources 目录绝对路径(缺省不播种——readResource 未绑定即显式抛错) */\n resourcesDir?: string;\n}\n\n/**\n * sub-agent 派发工具名前缀(与 injection/subAgentToolName.ts 的 SUB_AGENT_TOOL_PREFIX\n * 同步——本文件自包含不可值导入,taskWorker.test.ts 断言完整派发名锚定漂移)。\n * 快照内的 agent 名来自文件注册表(scanAgents 构建期已校验字符集),直拼即合法。\n */\nconst SUB_AGENT_TOOL_PREFIX = 'agent-';\n\n/** worker 错误回传负载(serializeError 的产物,宿主 reviveError 重建) */\nexport interface SerializedWorkerError {\n name: string;\n message: string;\n stack: string | undefined;\n props: Record<string, unknown>;\n}\n\n/**\n * worker 内重建注册表只读视图(语义与宿主 createTaskRegistriesView 一致:\n * 不存在的 tool/agent 名静默跳过、resolveAgentTools 按解析后 name 去重)\n *\n * 快照为纯数据(函数闭包不可跨线程),视图在 worker 内重建。\n */\nexport function buildRegistriesView(snapshot?: TaskRegistriesSnapshot) {\n const agents = new Map((snapshot?.agents ?? []).map((a) => [a.name, a] as const));\n const tools = new Map((snapshot?.tools ?? []).map((t) => [t.name, t] as const));\n const skills = new Map((snapshot?.skills ?? []).map((s) => [s.name, s] as const));\n return {\n agent: {\n getAgent: (name: string) => agents.get(name),\n getAgentEntry: (name: string) => agents.get(name),\n listAgents: () => Array.from(agents.values()),\n asTool: (name: string) => {\n const agent = agents.get(name);\n if (!agent) return undefined;\n return {\n kind: 'agent' as const,\n name: `${SUB_AGENT_TOOL_PREFIX}${agent.name}`,\n agentName: agent.name,\n description: agent.description,\n metadata: agent,\n };\n },\n resolveAgentTools: (name: string) => {\n const agent = agents.get(name);\n const result = new Map<string, TaskRegistriesSnapshot['tools'][number]>();\n if (agent?.tools) {\n for (const toolName of agent.tools) {\n const resolved = tools.get(toolName);\n if (resolved) result.set(resolved.name, resolved);\n }\n }\n return Array.from(result.values());\n },\n resolveSubAgents: (name: string) => {\n const agent = agents.get(name);\n const result: TaskRegistriesSnapshot['agents'] = [];\n if (agent?.agents) {\n for (const subName of agent.agents) {\n const sub = agents.get(subName);\n if (sub) result.push(sub);\n }\n }\n return result;\n },\n },\n tool: {\n get: (name: string) => tools.get(name),\n list: () => Array.from(tools.values()),\n },\n skill: {\n get: (name: string) => skills.get(name),\n list: () => Array.from(skills.values()),\n },\n };\n}\n\n/**\n * worker 内错误序列化(错误保真):Error 按 `{ name, message, stack, props }` 回传——\n * `props` 收集自定义可枚举属性(业务错误类的 code/statusCode 等),宿主侧 reviveError\n * 重建时回填;非 Error 值按 String(err) 归一。结构化克隆只保 message(name/自定义\n * 属性丢失、stack 重新生成),不序列化就无法跨线程保真。\n */\nexport function serializeError(err: unknown): SerializedWorkerError {\n if (err instanceof Error) {\n const props: Record<string, unknown> = {};\n for (const key of Object.keys(err)) {\n props[key] = (err as unknown as Record<string, unknown>)[key];\n }\n return { name: err.name, message: err.message, stack: err.stack, props };\n }\n return { name: 'Error', message: String(err), stack: undefined, props: {} };\n}\n\nconst LOG_RANK: Record<LogLevel, number> = { debug: 0, info: 1, warn: 2, error: 3 };\n\n/**\n * worker 内重建轻量 LLM 补全通道(隔离任务路径)\n *\n * channel 是函数闭包不可跨线程;`agent.llms` 是纯数据——postMessage 传入后在本\n * 函数内动态加载 `@faapi/agent` 重建实例(与 task.ts 内 import '@faapi/agent'\n * 同一解析来源,业务方安装即可用)。specifier 变量拼接避免主包静态依赖\n * `@faapi/agent`(与 loadTaskDriver 加载驱动子包同策略)。\n *\n * 降级路径(已记入 fallback.md):`@faapi/agent` 未安装/不可解析时 `console.warn`\n * 留痕并返回 `undefined`——taskCtx.llm 为 undefined,任务自身决定报错或旁路;\n * llms 未配置时不触发加载。\n */\nexport async function buildLlmChannel(\n llms?: Record<string, LlmConfig>,\n): Promise<LlmComplete | undefined> {\n if (!llms || Object.keys(llms).length === 0) return undefined;\n const specifier = '@faapi/' + 'agent';\n try {\n const mod = (await import(/* @vite-ignore */ specifier)) as {\n createLightComplete?: (deps: { llms: Record<string, LlmConfig> }) => LlmComplete;\n };\n if (typeof mod.createLightComplete !== 'function') {\n console.warn(\n '[faapi] @faapi/agent resolved but does not export createLightComplete() — taskCtx.llm unavailable (check package version)',\n );\n return undefined;\n }\n return mod.createLightComplete({ llms });\n } catch {\n console.warn(\n '[faapi] agent.llms is configured but @faapi/agent is not resolvable in the task worker — install @faapi/agent to enable taskCtx.llm (task execution continues)',\n );\n return undefined;\n }\n}\n\n/**\n * 任务日志器:级别预过滤(level 为 undefined 时不过滤——宿主文件管道默认全量)+\n * scope/fields 组装,条目作为纯数据经 `{ type: 'log' }` 消息回传宿主、由宿主统一\n * sink 输出(语义与主包 createLogger 对齐:child scope `:` 合并、调用处 fields\n * 覆盖构造字段;格式化在宿主侧,不跨线程复制格式代码)。\n *\n * fields 含不可克隆值时丢弃 fields、保底 level/message/scope(降级已记入\n * fallback.md)——日志永不中断任务执行,与 progress 的\"不可克隆按执行错误处理\"\n * 不同。\n */\nexport function createTaskLogger(\n level?: LogLevel,\n scope?: string,\n fields?: Record<string, unknown>,\n) {\n const make = (suffix?: string) => {\n const write = (lvl: LogLevel, message: string, callFields?: Record<string, unknown>) => {\n if (level !== undefined && LOG_RANK[lvl] < LOG_RANK[level]) return;\n const entry: LogEntry = { level: lvl, message, time: new Date().toISOString() };\n const fullScope =\n suffix === undefined ? scope : scope === undefined ? suffix : `${scope}:${suffix}`;\n if (fullScope !== undefined) entry.scope = fullScope;\n if (fields !== undefined || callFields !== undefined) {\n entry.fields = { ...fields, ...callFields };\n }\n try {\n parentPort?.postMessage({ type: 'log', entry });\n } catch {\n entry.fields = { warning: 'log fields not cloneable across worker boundary, dropped' };\n parentPort?.postMessage({ type: 'log', entry });\n }\n };\n return {\n debug: (m: string, f?: Record<string, unknown>) => write('debug', m, f),\n info: (m: string, f?: Record<string, unknown>) => write('info', m, f),\n warn: (m: string, f?: Record<string, unknown>) => write('warn', m, f),\n error: (m: string, f?: Record<string, unknown>) => write('error', m, f),\n child: (cs: string) => make(suffix === undefined ? cs : `${suffix}:${cs}`),\n };\n };\n return make(undefined);\n}\n\n/**\n * worker 侧 bootstrap:播种读取根 + 桥接 run 消息\n *\n * 仅在 worker 线程执行(主线程 import 本模块时 parentPort 为空,只取上面的纯\n * 函数)。任务模块在首次 run 消息时才动态 import(modulePromise 缓存)——播种\n * 语句先于它执行,任务模块顶层代码(top-level await 调用 readResource)读到\n * 的绑定必然已就位。\n */\nfunction bootstrap(): void {\n if (!parentPort) return;\n const data = workerData as WorkerEntryData;\n\n // 播种全局 readResource 读取根(缺省不播种——直接构造队列的测试/嵌入场景,\n // readResource 未绑定即显式抛错)\n if (data.resourcesDir) {\n (globalThis as Record<symbol, string | undefined>)[Symbol.for('faapi.resources.dir')] =\n data.resourcesDir;\n }\n\n let controller: AbortController | null = null;\n let modulePromise: Promise<{ run?: (payload: unknown, taskCtx: unknown) => unknown }> | null =\n null;\n\n parentPort.on(\n 'message',\n (msg: {\n type?: string;\n reason?: string;\n payload?: unknown;\n taskCtx?: Record<string, unknown>;\n registries?: TaskRegistriesSnapshot;\n llms?: Record<string, LlmConfig>;\n log?: { level?: LogLevel; scope?: string; fields?: Record<string, unknown> };\n }) => {\n if (msg?.type === 'abort') {\n controller?.abort(new Error(msg.reason));\n return;\n }\n if (msg?.type !== 'run') return;\n controller = new AbortController();\n const { payload, taskCtx } = msg;\n void (async () => {\n try {\n modulePromise ??= import(data.moduleUrl);\n const mod = await modulePromise;\n const run = mod.run;\n if (typeof run !== 'function') {\n parentPort?.postMessage({\n type: 'error',\n error: {\n name: 'Error',\n message: 'Task module has no run export',\n stack: undefined,\n props: {},\n },\n });\n return;\n }\n const registries = buildRegistriesView(msg.registries);\n const progress = (value: unknown) => parentPort?.postMessage({ type: 'progress', value });\n const taskLog = msg.log\n ? createTaskLogger(msg.log.level, msg.log.scope, msg.log.fields)\n : undefined;\n // 轻量补全通道:llms 快照在 worker 内重建(不可解析时 undefined,warn 留痕)\n const llm = await buildLlmChannel(msg.llms);\n const result = await run(payload, {\n ...taskCtx,\n signal: controller!.signal,\n registries,\n llm,\n progress,\n log: taskLog,\n });\n parentPort?.postMessage({ type: 'done', result });\n } catch (err) {\n const serialized = serializeError(err);\n try {\n parentPort?.postMessage({ type: 'error', error: serialized });\n } catch {\n // props 含不可克隆值(函数等)时丢弃 props,保底 name/message/stack\n parentPort?.postMessage({\n type: 'error',\n error: {\n name: serialized.name,\n message: serialized.message,\n stack: serialized.stack,\n props: {},\n },\n });\n }\n }\n })();\n },\n );\n}\n\nbootstrap();\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
import { F as FaapiContext } from './contextTypes-DhagvRH_.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* faapi 中间件(洋葱模型)
|
|
5
|
-
*
|
|
6
|
-
* 单一 async 函数,通过 `await next()` 衔接前置/后置逻辑:
|
|
7
|
-
* - `await next()` 之前的代码:前置处理(鉴权、日志开始计时等)
|
|
8
|
-
* - `await next()` 之后的代码:后置处理(日志输出、响应修改等)
|
|
9
|
-
* - 不调用 `next()` 即拦截请求(如鉴权失败直接返回 Response)
|
|
10
|
-
* - `next()` 返回内层 Response,中间件可选择使用或替换
|
|
11
|
-
* - 返回 `Response`:作为响应返回(可用于拦截或错误处理)
|
|
12
|
-
* - 返回 `void`:使用 `await next()` 返回的内层响应
|
|
13
|
-
*
|
|
14
|
-
* 错误处理用 try/catch 包裹 `await next()`,而非独立的 error 钩子。
|
|
15
|
-
*
|
|
16
|
-
* 执行顺序(洋葱模型):
|
|
17
|
-
* ```
|
|
18
|
-
* mw1.before → mw2.before → handler → mw2.after → mw1.after
|
|
19
|
-
* ```
|
|
20
|
-
*
|
|
21
|
-
* 示例 middlewares.ts:
|
|
22
|
-
* ```ts
|
|
23
|
-
* import type { FaapiMiddleware } from '@faapi/faapi';
|
|
24
|
-
*
|
|
25
|
-
* export default [
|
|
26
|
-
* // 鉴权:不调 next() 即拦截
|
|
27
|
-
* async (ctx, next) => {
|
|
28
|
-
* const token = ctx.headers.get('authorization');
|
|
29
|
-
* if (!token) return new Response('Unauthorized', { status: 401 });
|
|
30
|
-
* ctx.user = await verifyToken(token);
|
|
31
|
-
* await next();
|
|
32
|
-
* },
|
|
33
|
-
* // 日志:before/after 一体,闭包共享状态
|
|
34
|
-
* async (ctx, next) => {
|
|
35
|
-
* const start = Date.now();
|
|
36
|
-
* await next();
|
|
37
|
-
* console.log(`${ctx.method} ${ctx.path} ${Date.now() - start}ms`);
|
|
38
|
-
* },
|
|
39
|
-
* // 错误处理:try/catch 语义
|
|
40
|
-
* async (ctx, next) => {
|
|
41
|
-
* try {
|
|
42
|
-
* await next();
|
|
43
|
-
* } catch (err) {
|
|
44
|
-
* return new Response(JSON.stringify({ error: String(err) }), { status: 500 });
|
|
45
|
-
* }
|
|
46
|
-
* },
|
|
47
|
-
* ] satisfies FaapiMiddleware[];
|
|
48
|
-
* ```
|
|
49
|
-
*/
|
|
50
|
-
type FaapiMiddleware = (ctx: FaapiContext, next: () => Promise<Response>) => Promise<void | Response>;
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* 注入器:按参数名匹配,提供 handler 所需的依赖
|
|
54
|
-
*
|
|
55
|
-
* 注入器是 faapi 的依赖注入扩展点,与中间件解耦:
|
|
56
|
-
* - 中间件只管请求流程(鉴权、日志、错误处理)
|
|
57
|
-
* - 注入器只管提供依赖(数据库连接、用户对象等)
|
|
58
|
-
*
|
|
59
|
-
* 注入器可以读取中间件塞进 ctx 的值(如鉴权中间件塞的 ctx.user),
|
|
60
|
-
* 也可以独立提供依赖(如数据库连接池)。
|
|
61
|
-
*
|
|
62
|
-
* 注入器按需执行:只对 handler 声明的参数执行对应的注入器,避免无谓计算。
|
|
63
|
-
*
|
|
64
|
-
* 在 middlewares.ts 中通过命名导出 `injectors` 注册:
|
|
65
|
-
* ```ts
|
|
66
|
-
* import type { InjectorMap } from '@faapi/faapi';
|
|
67
|
-
*
|
|
68
|
-
* export const injectors: InjectorMap = {
|
|
69
|
-
* db: () => getDbConnection(),
|
|
70
|
-
* user: (ctx) => ctx.user, // 取中间件塞的值
|
|
71
|
-
* };
|
|
72
|
-
* ```
|
|
73
|
-
*/
|
|
74
|
-
type Injector = (ctx: FaapiContext) => unknown | Promise<unknown>;
|
|
75
|
-
/**
|
|
76
|
-
* 注入器映射表:参数名 → 注入器函数
|
|
77
|
-
*
|
|
78
|
-
* key 必须与 handler 参数名一致,运行时按参数名匹配执行。
|
|
79
|
-
*/
|
|
80
|
-
type InjectorMap = Record<string, Injector>;
|
|
81
|
-
|
|
82
|
-
interface CorsOptions {
|
|
83
|
-
origin?: string | string[] | true;
|
|
84
|
-
methods?: string[];
|
|
85
|
-
allowedHeaders?: string[];
|
|
86
|
-
exposeHeaders?: string[];
|
|
87
|
-
credentials?: boolean;
|
|
88
|
-
maxAge?: number;
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* 创建 CORS 中间件(洋葱模型)
|
|
92
|
-
*
|
|
93
|
-
* - origin=true: 允许所有来源(反射请求的 Origin)
|
|
94
|
-
* - origin=string: 允许指定来源
|
|
95
|
-
* - origin=string[]: 允许多个来源
|
|
96
|
-
*
|
|
97
|
-
* OPTIONS 预检请求直接返回 204,不调用 next()。
|
|
98
|
-
*/
|
|
99
|
-
declare function cors(options?: CorsOptions): FaapiMiddleware;
|
|
100
|
-
|
|
101
|
-
interface HelmetOptions {
|
|
102
|
-
contentSecurityPolicy?: string | false;
|
|
103
|
-
xFrameOptions?: 'DENY' | 'SAMEORIGIN' | false;
|
|
104
|
-
xContentTypeOptions?: boolean;
|
|
105
|
-
referrerPolicy?: string | false;
|
|
106
|
-
strictTransportSecurity?: string | false;
|
|
107
|
-
xDnsPrefetchControl?: boolean;
|
|
108
|
-
xDownloadOptions?: boolean;
|
|
109
|
-
xPermittedCrossDomainPolicies?: string | false;
|
|
110
|
-
crossOriginOpenerPolicy?: string | false;
|
|
111
|
-
crossOriginResourcePolicy?: string | false;
|
|
112
|
-
crossOriginEmbedderPolicy?: string | false;
|
|
113
|
-
originAgentCluster?: boolean;
|
|
114
|
-
xPoweredBy?: boolean;
|
|
115
|
-
}
|
|
116
|
-
declare function helmet(options?: HelmetOptions): FaapiMiddleware;
|
|
117
|
-
|
|
118
|
-
declare const HTTP_METHODS: readonly ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
119
|
-
type HttpMethod = (typeof HTTP_METHODS)[number];
|
|
120
|
-
|
|
121
|
-
interface RouteRecord {
|
|
122
|
-
method: HttpMethod;
|
|
123
|
-
urlPath: string;
|
|
124
|
-
filePath: string;
|
|
125
|
-
paramNames: string[];
|
|
126
|
-
isDynamic: boolean;
|
|
127
|
-
/** 是否为 catch-all 路由([...slug]) */
|
|
128
|
-
isCatchAll?: boolean;
|
|
129
|
-
/** 中间件文件绝对路径列表(根在前,路由目录在后;按需加载用) */
|
|
130
|
-
middlewarePaths?: string[];
|
|
131
|
-
/** 路由对应的中间件集合(从根到路由目录合并,按需加载后缓存) */
|
|
132
|
-
middlewares?: FaapiMiddleware[];
|
|
133
|
-
/** 路由对应的注入器映射表(从根到路由目录合并,按需加载后缓存) */
|
|
134
|
-
injectors?: InjectorMap;
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* WebSocket 路由记录
|
|
138
|
-
*
|
|
139
|
-
* 与 HTTP RouteRecord 类似,但不绑定 HTTP 方法(WS 是协议升级,不区分 GET/POST)。
|
|
140
|
-
* 一个 handler.ts 中导出 WS 即生成一条 WS 路由记录。
|
|
141
|
-
*/
|
|
142
|
-
interface WsRouteRecord {
|
|
143
|
-
urlPath: string;
|
|
144
|
-
filePath: string;
|
|
145
|
-
paramNames: string[];
|
|
146
|
-
isDynamic: boolean;
|
|
147
|
-
/** 是否为 catch-all 路由([...slug]) */
|
|
148
|
-
isCatchAll?: boolean;
|
|
149
|
-
/** 中间件文件绝对路径列表(根在前,路由目录在后;按需加载用) */
|
|
150
|
-
middlewarePaths?: string[];
|
|
151
|
-
/** 路由对应的中间件集合(握手阶段执行,复用鉴权/CORS/日志;按需加载后缓存) */
|
|
152
|
-
middlewares?: FaapiMiddleware[];
|
|
153
|
-
/** 路由对应的注入器映射表 */
|
|
154
|
-
injectors?: InjectorMap;
|
|
155
|
-
}
|
|
156
|
-
type RouteManifest = RouteRecord[];
|
|
157
|
-
type WsRouteManifest = WsRouteRecord[];
|
|
158
|
-
/**
|
|
159
|
-
* 路由单个参数的 schema 描述
|
|
160
|
-
*
|
|
161
|
-
* 供 @faapi/schema 扩展包消费,通过 MCP 暴露给 LLM。
|
|
162
|
-
*/
|
|
163
|
-
interface RouteParamSchema {
|
|
164
|
-
name: string;
|
|
165
|
-
type: string;
|
|
166
|
-
required: boolean;
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* 路由单个输入源的 schema 描述
|
|
170
|
-
*/
|
|
171
|
-
interface RouteInputSchema {
|
|
172
|
-
source: 'query' | 'body' | 'params';
|
|
173
|
-
schemaName: string | null;
|
|
174
|
-
properties: RouteParamSchema[];
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* 路由响应类型的 schema 描述
|
|
178
|
-
*
|
|
179
|
-
* 由 @faapi/schema 扩展包的 buildRouteSchemas 生成。
|
|
180
|
-
* output 为 null 表示无显式返回类型注解、void/Promise<void>、或解析失败降级。
|
|
181
|
-
*/
|
|
182
|
-
interface RouteOutputSchema {
|
|
183
|
-
/** 命名类型名(如 'UserResponse'),内联类型为 null */
|
|
184
|
-
schemaName: string | null;
|
|
185
|
-
/** 顶层属性列表 */
|
|
186
|
-
properties: RouteParamSchema[];
|
|
187
|
-
}
|
|
188
|
-
/**
|
|
189
|
-
* 路由的完整 schema 描述
|
|
190
|
-
*
|
|
191
|
-
* 由 @faapi/schema 扩展包的 buildRouteSchemas 生成。
|
|
192
|
-
* 主包只定义类型契约,逻辑实现在扩展包。
|
|
193
|
-
*/
|
|
194
|
-
interface RouteInfo {
|
|
195
|
-
method: string;
|
|
196
|
-
path: string;
|
|
197
|
-
filePath: string;
|
|
198
|
-
isDynamic: boolean;
|
|
199
|
-
inputs: RouteInputSchema[];
|
|
200
|
-
/** 响应类型描述(null 表示无返回类型注解/void/解析失败) */
|
|
201
|
-
output: RouteOutputSchema | null;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export { type CorsOptions as C, type FaapiMiddleware as F, type HelmetOptions as H, type InjectorMap as I, type RouteManifest as R, type WsRouteManifest as W, type Injector as a, type RouteInfo as b, type RouteInputSchema as c, type RouteOutputSchema as d, type RouteParamSchema as e, cors as f, helmet as h };
|