@faapi/faapi 6.7.0 → 6.8.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 +196 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +43 -3
- package/dist/index.js +186 -4
- package/dist/index.js.map +1 -1
- package/dist/{routeTypes-CsL7xtqu.d.ts → routeTypes-J5pPbjwR.d.ts} +104 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +97 -1
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
|
@@ -249,6 +249,70 @@ interface TaskDriver {
|
|
|
249
249
|
retry?(name: string, id: string): Promise<void>;
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
+
/**
|
|
253
|
+
* 日志子系统类型契约(logger.md)
|
|
254
|
+
*
|
|
255
|
+
* Logger 跨越 handler(ctx.log / 参数 log)、任务(taskCtx.log)与任意业务代码
|
|
256
|
+
* (createLogger)多条路径,类型是各路径形状一致的单一来源;
|
|
257
|
+
* LogEntry 为纯数据(可结构化克隆),是隔离任务日志跨线程回传的契约。
|
|
258
|
+
*/
|
|
259
|
+
/** 日志级别(阈值序:debug < info < warn < error) */
|
|
260
|
+
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
261
|
+
/**
|
|
262
|
+
* 单条日志(sink 的输入,纯数据可结构化克隆)
|
|
263
|
+
*
|
|
264
|
+
* time 为 ISO 字符串(写入时刻生成);scope/fields 可省略。
|
|
265
|
+
*/
|
|
266
|
+
interface LogEntry {
|
|
267
|
+
level: LogLevel;
|
|
268
|
+
message: string;
|
|
269
|
+
time: string;
|
|
270
|
+
scope?: string;
|
|
271
|
+
fields?: Record<string, unknown>;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* 日志输出目标(默认 console 文本,可整体接管接 pino/winston/文件)
|
|
275
|
+
*
|
|
276
|
+
* sink 内抛错会被吞掉(日志永不影响业务流程)。
|
|
277
|
+
*/
|
|
278
|
+
type LogSink = (entry: LogEntry) => void;
|
|
279
|
+
/**
|
|
280
|
+
* 日志器接口
|
|
281
|
+
*
|
|
282
|
+
* 方法签名统一为 `message` 在前、结构化字段可选在后;
|
|
283
|
+
* `child(scope)` 返回新 Logger(scope 以 `:` 合并、fields 浅合并,父子互不影响)。
|
|
284
|
+
*/
|
|
285
|
+
interface Logger {
|
|
286
|
+
debug(message: string, fields?: Record<string, unknown>): void;
|
|
287
|
+
info(message: string, fields?: Record<string, unknown>): void;
|
|
288
|
+
warn(message: string, fields?: Record<string, unknown>): void;
|
|
289
|
+
error(message: string, fields?: Record<string, unknown>): void;
|
|
290
|
+
child(scope: string): Logger;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* 日志器构造选项(实例级覆盖全局)
|
|
294
|
+
*
|
|
295
|
+
* 命名带 CreateLogger 前缀:公开导出避免与请求日志中间件的 `LoggerOptions`
|
|
296
|
+
* (middleware/logger.ts,先于本模块存在)冲突。
|
|
297
|
+
*/
|
|
298
|
+
interface CreateLoggerOptions {
|
|
299
|
+
/** 实例级级别(覆盖全局 level) */
|
|
300
|
+
level?: LogLevel;
|
|
301
|
+
/** 实例级 sink(覆盖全局 sink;显式接管不受全局 `log: false` 影响) */
|
|
302
|
+
sink?: LogSink;
|
|
303
|
+
/** 构造字段(随每条日志携带,调用处 fields 同名键覆盖) */
|
|
304
|
+
fields?: Record<string, unknown>;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* `config.log` 的对象形状(config 侧另接受 boolean:false 全静默、true 默认)
|
|
308
|
+
*
|
|
309
|
+
* level 解析顺序:显式值 > `LOG_LEVEL` 环境变量 > `'info'`(非法值启动报错)。
|
|
310
|
+
*/
|
|
311
|
+
interface LogConfig {
|
|
312
|
+
level?: LogLevel;
|
|
313
|
+
sink?: LogSink;
|
|
314
|
+
}
|
|
315
|
+
|
|
252
316
|
/**
|
|
253
317
|
* 隔离执行器签名(taskQueue 按任务 meta.timeoutMs 调用;测试可注入 spy)
|
|
254
318
|
*/
|
|
@@ -279,6 +343,17 @@ interface TaskWorkerOptions {
|
|
|
279
343
|
* taskCtx.registries)——语义层从 `TaskRegistriesView` 生成,缺省为空视图
|
|
280
344
|
*/
|
|
281
345
|
registries?: TaskRegistriesSnapshot;
|
|
346
|
+
/**
|
|
347
|
+
* 任务日志配置(纯数据,postMessage 传入,worker 内联重建日志器注入 taskCtx.log;
|
|
348
|
+
* 缺省时 taskCtx.log 为 undefined):scope/fields 与进程内路径一致(`task:<name>`
|
|
349
|
+
* + jobId/task/attempt),level 为宿主侧生效的全局级别(worker 侧预过滤,
|
|
350
|
+
* 宿主 writeLogEntry 再次过滤)
|
|
351
|
+
*/
|
|
352
|
+
log?: {
|
|
353
|
+
level: LogLevel;
|
|
354
|
+
scope?: string;
|
|
355
|
+
fields?: Record<string, unknown>;
|
|
356
|
+
};
|
|
282
357
|
/** 外部取消信号(驱动停机超时 abort)——abort 同样触发两段式取消 */
|
|
283
358
|
externalSignal?: AbortSignal;
|
|
284
359
|
/**
|
|
@@ -286,6 +361,12 @@ interface TaskWorkerOptions {
|
|
|
286
361
|
* 消息回传宿主(语义层记入 `TaskJob.progress`);不传则进度消息被忽略
|
|
287
362
|
*/
|
|
288
363
|
onProgress?: (value: unknown) => void;
|
|
364
|
+
/**
|
|
365
|
+
* 日志回调:worker 内 taskCtx.log 的条目经 `{ type: 'log' }` 消息回传宿主,
|
|
366
|
+
* 由语义层接 writeLogEntry 走统一管道(自定义 sink 同样覆盖隔离任务);
|
|
367
|
+
* 不传则日志条目被忽略。宽限期(取消判定后)到达的条目不采纳(超时判定即终局)
|
|
368
|
+
*/
|
|
369
|
+
onLog?: (entry: LogEntry) => void;
|
|
289
370
|
}
|
|
290
371
|
declare function runTaskInWorker(options: TaskWorkerOptions): Promise<unknown>;
|
|
291
372
|
|
|
@@ -521,6 +602,13 @@ interface TaskContext {
|
|
|
521
602
|
* 不可克隆按执行错误处理)。仅 running 状态生效,终态后调用被忽略。
|
|
522
603
|
*/
|
|
523
604
|
progress?: (value: unknown) => void;
|
|
605
|
+
/**
|
|
606
|
+
* 任务级日志器(可选字段;框架两条执行路径均注入,直接构造 TaskContext 的
|
|
607
|
+
* 测试/自定义执行器可不传)——scope `task:<name>`,字段自动携带
|
|
608
|
+
* jobId/task/attempt,输出走 `config.log` 统一管道(详见 logger/logger.md)。
|
|
609
|
+
* 隔离执行时条目经 postMessage 回传宿主输出,fields 需可结构化克隆。
|
|
610
|
+
*/
|
|
611
|
+
log?: Logger;
|
|
524
612
|
}
|
|
525
613
|
/**
|
|
526
614
|
* 任务模块形态(task.ts 编译产物中与执行相关的导出)
|
|
@@ -773,6 +861,21 @@ interface FaapiContext {
|
|
|
773
861
|
* 无 app 编排(编程式直调 ctx)时为 undefined
|
|
774
862
|
*/
|
|
775
863
|
tasks?: TaskClient;
|
|
864
|
+
/**
|
|
865
|
+
* 请求 ID:请求头 `x-request-id` 第一段(网关透传场景跨服务串联),无则 crypto.randomUUID() 生成。
|
|
866
|
+
* ctx.log 与请求日志中间件的条目均携带该字段,业务日志与请求日志可经此关联
|
|
867
|
+
*/
|
|
868
|
+
requestId: string;
|
|
869
|
+
/**
|
|
870
|
+
* 请求级日志器(scope `http`,自动携带 requestId/method/path 字段,详见 logger/logger.md)
|
|
871
|
+
*
|
|
872
|
+
* ```ts
|
|
873
|
+
* export function GET(ctx) {
|
|
874
|
+
* ctx.log.info('listing users');
|
|
875
|
+
* }
|
|
876
|
+
* ```
|
|
877
|
+
*/
|
|
878
|
+
log: Logger;
|
|
776
879
|
request: Request;
|
|
777
880
|
params: Record<string, string>;
|
|
778
881
|
query: URLSearchParams;
|
|
@@ -1137,4 +1240,4 @@ interface RouteInfo {
|
|
|
1137
1240
|
output: RouteOutputSchema | null;
|
|
1138
1241
|
}
|
|
1139
1242
|
|
|
1140
|
-
export {
|
|
1243
|
+
export { type ToolPathMeta as $, type AppRegistries as A, type RouteInputSchema as B, type CorsOptions as C, type RouteOutputSchema as D, type RouteParamSchema as E, type FaapiContext as F, type SseEvent as G, type HelmetOptions as H, type InjectorMap as I, type SseWriter as J, type TaskContext as K, type LoggerOptions as L, type TaskDriverJob as M, type TaskDriverProcess as N, type TaskDriverRecord as O, type TaskFailedInfo as P, type TaskJob as Q, type RouteManifest as R, type SkillRegistry as S, type TaskClient as T, type TaskJobStatus as U, type TaskMetadata as V, type WsRouteManifest as W, type TaskModule as X, type TaskRegistriesSnapshot as Y, type TaskRegistriesView as Z, type ToolCore as _, type LogConfig as a, type ToolRegistry as a0, cors as a1, createAppRegistries as a2, createTaskRegistriesView as a3, createTaskRegistry as a4, helmet as a5, logger as a6, type FaapiMiddleware as b, type TaskFailedHandler as c, type TaskQueueDeps as d, type TaskQueue as e, type TaskDriver as f, type TaskRegistry as g, type TaskManifest as h, type ToolMetadata as i, type AgentCore as j, type AgentMetadata as k, type CreateLoggerOptions as l, type Logger as m, type AgentHandleFactory as n, type AgentHandleStore as o, type AgentPathMeta as p, type AgentRegistry as q, type AgentToolDescriptor as r, type FaapiContextConfig as s, type FaapiTaskMeta as t, type FailOptions as u, type Injector as v, type LogEntry as w, type LogLevel as x, type LogSink as y, type RouteInfo as z };
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { F as FaapiContext,
|
|
1
|
+
import { F as FaapiContext, b as FaapiMiddleware, I as InjectorMap, R as RouteManifest, W as WsRouteManifest, C as CorsOptions, H as HelmetOptions, L as LoggerOptions } from './routeTypes-J5pPbjwR.js';
|
|
2
2
|
import { Server } from 'node:http';
|
|
3
3
|
import { WebSocket } from 'ws';
|
|
4
4
|
|
package/dist/testing.js
CHANGED
|
@@ -245,7 +245,90 @@ function formatErrorResponse(error, config) {
|
|
|
245
245
|
return jsonOk(body, 500);
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
+
// src/logger/logger.ts
|
|
249
|
+
var LEVEL_RANK = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
250
|
+
var globalState = {
|
|
251
|
+
level: null,
|
|
252
|
+
sink: null,
|
|
253
|
+
disabled: false
|
|
254
|
+
};
|
|
255
|
+
function globalThreshold() {
|
|
256
|
+
return globalState.level ?? "info";
|
|
257
|
+
}
|
|
258
|
+
function serializeFieldValue(value) {
|
|
259
|
+
if (value instanceof Error) {
|
|
260
|
+
return { name: value.name, message: value.message, stack: value.stack };
|
|
261
|
+
}
|
|
262
|
+
return value;
|
|
263
|
+
}
|
|
264
|
+
function formatEntry(entry) {
|
|
265
|
+
let line = `[${entry.time}] ${entry.level.toUpperCase()} `;
|
|
266
|
+
if (entry.scope) line += `[${entry.scope}] `;
|
|
267
|
+
line += entry.message;
|
|
268
|
+
if (entry.fields !== void 0) {
|
|
269
|
+
const plain = Object.fromEntries(
|
|
270
|
+
Object.entries(entry.fields).map(([k, v]) => [k, serializeFieldValue(v)])
|
|
271
|
+
);
|
|
272
|
+
try {
|
|
273
|
+
line += ` ${JSON.stringify(plain)}`;
|
|
274
|
+
} catch (err) {
|
|
275
|
+
line += ` [unserializable fields: ${err instanceof Error ? err.message : String(err)}]`;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return line;
|
|
279
|
+
}
|
|
280
|
+
var CONSOLE_METHOD = {
|
|
281
|
+
debug: "debug",
|
|
282
|
+
info: "info",
|
|
283
|
+
warn: "warn",
|
|
284
|
+
error: "error"
|
|
285
|
+
};
|
|
286
|
+
var consoleSink = (entry) => {
|
|
287
|
+
console[CONSOLE_METHOD[entry.level]](formatEntry(entry));
|
|
288
|
+
};
|
|
289
|
+
function globalDispatch(entry) {
|
|
290
|
+
if (globalState.disabled) return;
|
|
291
|
+
(globalState.sink ?? consoleSink)(entry);
|
|
292
|
+
}
|
|
293
|
+
function createLogger(scope, options) {
|
|
294
|
+
const baseScope = scope;
|
|
295
|
+
const baseFields = options?.fields;
|
|
296
|
+
const write = (level, message, callFields, scopeSuffix) => {
|
|
297
|
+
const threshold = options?.level ?? globalThreshold();
|
|
298
|
+
if (LEVEL_RANK[level] < LEVEL_RANK[threshold]) return;
|
|
299
|
+
const entry = {
|
|
300
|
+
level,
|
|
301
|
+
message,
|
|
302
|
+
time: (/* @__PURE__ */ new Date()).toISOString()
|
|
303
|
+
};
|
|
304
|
+
const fullScope = scopeSuffix === void 0 ? baseScope : baseScope === void 0 ? scopeSuffix : `${baseScope}:${scopeSuffix}`;
|
|
305
|
+
if (fullScope !== void 0) entry.scope = fullScope;
|
|
306
|
+
if (baseFields !== void 0 || callFields !== void 0) {
|
|
307
|
+
entry.fields = { ...baseFields, ...callFields };
|
|
308
|
+
}
|
|
309
|
+
if (options?.sink) {
|
|
310
|
+
options.sink(entry);
|
|
311
|
+
} else {
|
|
312
|
+
globalDispatch(entry);
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
const makeLogger = (scopeSuffix) => ({
|
|
316
|
+
debug: (message, fields) => write("debug", message, fields, scopeSuffix),
|
|
317
|
+
info: (message, fields) => write("info", message, fields, scopeSuffix),
|
|
318
|
+
warn: (message, fields) => write("warn", message, fields, scopeSuffix),
|
|
319
|
+
error: (message, fields) => write("error", message, fields, scopeSuffix),
|
|
320
|
+
child: (childScope) => makeLogger(scopeSuffix === void 0 ? childScope : `${scopeSuffix}:${childScope}`)
|
|
321
|
+
});
|
|
322
|
+
return makeLogger(void 0);
|
|
323
|
+
}
|
|
324
|
+
|
|
248
325
|
// src/runtime/createContext.ts
|
|
326
|
+
function resolveRequestId(request) {
|
|
327
|
+
const header = request.headers.get("x-request-id");
|
|
328
|
+
const first = header?.split(",")[0]?.trim();
|
|
329
|
+
if (first) return first;
|
|
330
|
+
return crypto.randomUUID();
|
|
331
|
+
}
|
|
249
332
|
function parseCookies(cookieHeader) {
|
|
250
333
|
const cookies = /* @__PURE__ */ new Map();
|
|
251
334
|
if (!cookieHeader) return cookies;
|
|
@@ -279,7 +362,13 @@ function createContextFromUrl(request, url, params, config = {}, ip = "", regist
|
|
|
279
362
|
for (const [key, val] of parsedCookies) {
|
|
280
363
|
cookiesObj[key] = val;
|
|
281
364
|
}
|
|
365
|
+
const requestId = resolveRequestId(request);
|
|
366
|
+
const log = createLogger("http", {
|
|
367
|
+
fields: { requestId, method: request.method, path: url.pathname }
|
|
368
|
+
});
|
|
282
369
|
const ctx = {
|
|
370
|
+
requestId,
|
|
371
|
+
log,
|
|
283
372
|
request,
|
|
284
373
|
params,
|
|
285
374
|
query: url.searchParams,
|
|
@@ -553,8 +642,10 @@ var PARAM_TYPE_MAP = {
|
|
|
553
642
|
// Phase 2.3
|
|
554
643
|
agents: "agents",
|
|
555
644
|
// Phase 2.3
|
|
556
|
-
tasks: "tasks"
|
|
645
|
+
tasks: "tasks",
|
|
557
646
|
// 任务子系统:TaskClient(入队/查询)
|
|
647
|
+
log: "log"
|
|
648
|
+
// 请求级日志器(ctx.log 同一实例:scope http,自动带 requestId 等字段)
|
|
558
649
|
};
|
|
559
650
|
var injectionCache = /* @__PURE__ */ new WeakMap();
|
|
560
651
|
function resolveInjection(fn) {
|
|
@@ -872,6 +963,9 @@ function getBuiltinInjectionValue(type, ctx, body) {
|
|
|
872
963
|
// 任务子系统:注入 TaskClient(入队/查询);未注册工厂(无 app 编排)时 undefined
|
|
873
964
|
case "tasks":
|
|
874
965
|
return ctx.registries ? ctx.registries.taskHandle.get(ctx) : void 0;
|
|
966
|
+
// 请求级日志器:与 ctx.log 同一实例(scope http,自动带 requestId/method/path 字段)
|
|
967
|
+
case "log":
|
|
968
|
+
return ctx.log;
|
|
875
969
|
default:
|
|
876
970
|
return void 0;
|
|
877
971
|
}
|
|
@@ -4101,6 +4195,7 @@ function logger(options = {}) {
|
|
|
4101
4195
|
const response = await next();
|
|
4102
4196
|
const duration = Date.now() - start;
|
|
4103
4197
|
const entry = {
|
|
4198
|
+
requestId: ctx.requestId,
|
|
4104
4199
|
method: ctx.method,
|
|
4105
4200
|
path: ctx.path,
|
|
4106
4201
|
status: response.status,
|
|
@@ -4113,6 +4208,7 @@ function logger(options = {}) {
|
|
|
4113
4208
|
const message = err instanceof Error ? err.message : String(err);
|
|
4114
4209
|
const status = err?.statusCode ?? 500;
|
|
4115
4210
|
const entry = {
|
|
4211
|
+
requestId: ctx.requestId,
|
|
4116
4212
|
method: ctx.method,
|
|
4117
4213
|
path: ctx.path,
|
|
4118
4214
|
status,
|