@faapi/faapi 6.6.0 → 6.7.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 +143 -41
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +140 -39
- package/dist/index.js.map +1 -1
- package/dist/{routeTypes-q9OXB--o.d.ts → routeTypes-CsL7xtqu.d.ts} +38 -5
- package/dist/testing.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as AppRegistries, R as RouteManifest, F as FaapiContext, C as CorsOptions, T as TaskClient, H as HelmetOptions, L as LoggerOptions, a as FaapiMiddleware, I as InjectorMap, b as TaskFailedHandler, c as TaskQueueDeps, d as TaskQueue, e as TaskDriver, f as TaskRegistry, g as TaskManifest, h as ToolMetadata, i as AgentCore, j as AgentMetadata, W as WsRouteManifest } from './routeTypes-
|
|
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 Injector, t as RouteInfo, u as RouteInputSchema, v as RouteOutputSchema, w as RouteParamSchema, S as SkillRegistry, x as SseEvent, y as SseWriter, z as TaskContext, B as TaskDriverJob, D as TaskDriverProcess, E as TaskDriverRecord, G as TaskFailedInfo, J as TaskJob, K as TaskJobStatus, M as TaskMetadata, N as TaskModule, O as TaskRegistriesSnapshot, P as TaskRegistriesView, Q as ToolCore, U as ToolPathMeta, V as ToolRegistry, X as cors, Y as createAppRegistries, Z as createTaskRegistriesView, _ as createTaskRegistry, $ as helmet, a0 as logger } from './routeTypes-
|
|
1
|
+
import { A as AppRegistries, R as RouteManifest, F as FaapiContext, C as CorsOptions, T as TaskClient, H as HelmetOptions, L as LoggerOptions, a as FaapiMiddleware, I as InjectorMap, b as TaskFailedHandler, c as TaskQueueDeps, d as TaskQueue, e as TaskDriver, f as TaskRegistry, g as TaskManifest, h as ToolMetadata, i as AgentCore, j as AgentMetadata, W as WsRouteManifest } from './routeTypes-CsL7xtqu.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 Injector, t as RouteInfo, u as RouteInputSchema, v as RouteOutputSchema, w as RouteParamSchema, S as SkillRegistry, x as SseEvent, y as SseWriter, z as TaskContext, B as TaskDriverJob, D as TaskDriverProcess, E as TaskDriverRecord, G as TaskFailedInfo, J as TaskJob, K as TaskJobStatus, M as TaskMetadata, N as TaskModule, O as TaskRegistriesSnapshot, P as TaskRegistriesView, Q as ToolCore, U as ToolPathMeta, V as ToolRegistry, X as cors, Y as createAppRegistries, Z as createTaskRegistriesView, _ as createTaskRegistry, $ as helmet, a0 as logger } from './routeTypes-CsL7xtqu.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';
|
package/dist/index.js
CHANGED
|
@@ -2053,6 +2053,19 @@ function buildRegistriesView(snapshot) {
|
|
|
2053
2053
|
};
|
|
2054
2054
|
}
|
|
2055
2055
|
`;
|
|
2056
|
+
var SERIALIZE_ERROR_SOURCE = `
|
|
2057
|
+
function serializeError(err) {
|
|
2058
|
+
if (err instanceof Error) {
|
|
2059
|
+
var props = {};
|
|
2060
|
+
var keys = Object.keys(err);
|
|
2061
|
+
for (var i = 0; i < keys.length; i++) {
|
|
2062
|
+
props[keys[i]] = err[keys[i]];
|
|
2063
|
+
}
|
|
2064
|
+
return { name: err.name, message: err.message, stack: err.stack, props: props };
|
|
2065
|
+
}
|
|
2066
|
+
return { name: 'Error', message: String(err), stack: undefined, props: {} };
|
|
2067
|
+
}
|
|
2068
|
+
`;
|
|
2056
2069
|
function buildWrapperSource(moduleUrl) {
|
|
2057
2070
|
return `
|
|
2058
2071
|
import { parentPort } from 'node:worker_threads';
|
|
@@ -2060,9 +2073,14 @@ import * as mod from '${moduleUrl}';
|
|
|
2060
2073
|
|
|
2061
2074
|
${BUILD_VIEW_SOURCE}
|
|
2062
2075
|
|
|
2076
|
+
${SERIALIZE_ERROR_SOURCE}
|
|
2077
|
+
|
|
2063
2078
|
const run = mod.run;
|
|
2064
2079
|
if (typeof run !== 'function') {
|
|
2065
|
-
parentPort.postMessage({
|
|
2080
|
+
parentPort.postMessage({
|
|
2081
|
+
type: 'error',
|
|
2082
|
+
error: { name: 'Error', message: 'Task module has no run export', stack: undefined, props: {} },
|
|
2083
|
+
});
|
|
2066
2084
|
} else {
|
|
2067
2085
|
let controller = null;
|
|
2068
2086
|
parentPort.on('message', async (msg) => {
|
|
@@ -2075,21 +2093,44 @@ if (typeof run !== 'function') {
|
|
|
2075
2093
|
const { payload, taskCtx } = msg;
|
|
2076
2094
|
try {
|
|
2077
2095
|
const registries = buildRegistriesView(msg.registries);
|
|
2078
|
-
const
|
|
2096
|
+
const progress = (value) => parentPort.postMessage({ type: 'progress', value });
|
|
2097
|
+
const result = await run(payload, { ...taskCtx, signal: controller.signal, registries, progress });
|
|
2079
2098
|
parentPort.postMessage({ type: 'done', result });
|
|
2080
2099
|
} catch (err) {
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
})
|
|
2100
|
+
const serialized = serializeError(err);
|
|
2101
|
+
try {
|
|
2102
|
+
parentPort.postMessage({ type: 'error', error: serialized });
|
|
2103
|
+
} catch (_) {
|
|
2104
|
+
// props \u542B\u4E0D\u53EF\u514B\u9686\u503C\uFF08\u51FD\u6570\u7B49\uFF09\u65F6\u4E22\u5F03 props\uFF0C\u4FDD\u5E95 name/message/stack
|
|
2105
|
+
parentPort.postMessage({
|
|
2106
|
+
type: 'error',
|
|
2107
|
+
error: { name: serialized.name, message: serialized.message, stack: serialized.stack, props: {} },
|
|
2108
|
+
});
|
|
2109
|
+
}
|
|
2085
2110
|
}
|
|
2086
2111
|
});
|
|
2087
2112
|
}
|
|
2088
2113
|
`;
|
|
2089
2114
|
}
|
|
2115
|
+
function reviveError(payload) {
|
|
2116
|
+
const err = new Error(payload?.message ?? "task worker error");
|
|
2117
|
+
if (payload?.name !== void 0) err.name = payload.name;
|
|
2118
|
+
if (payload?.stack !== void 0) err.stack = payload.stack;
|
|
2119
|
+
if (payload?.props) {
|
|
2120
|
+
for (const [key, value] of Object.entries(payload.props)) {
|
|
2121
|
+
err[key] = value;
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
return err;
|
|
2125
|
+
}
|
|
2090
2126
|
async function runTaskInWorker(options) {
|
|
2091
2127
|
const { taskModulePath, payload, taskCtx, timeoutMs, externalSignal } = options;
|
|
2092
|
-
const killGraceMs = options.
|
|
2128
|
+
const killGraceMs = options.graceMs ?? KILL_GRACE_MS;
|
|
2129
|
+
if (externalSignal?.aborted) {
|
|
2130
|
+
throw new TaskCancelledError(
|
|
2131
|
+
`Task "${taskCtx.job.name}" cancelled before start (driver stop signal already aborted)`
|
|
2132
|
+
);
|
|
2133
|
+
}
|
|
2093
2134
|
const moduleUrl = pathToFileURL(taskModulePath).href;
|
|
2094
2135
|
const wrapperUrl = new URL(
|
|
2095
2136
|
`data:text/javascript,${encodeURIComponent(buildWrapperSource(moduleUrl))}`
|
|
@@ -2137,27 +2178,37 @@ async function runTaskInWorker(options) {
|
|
|
2137
2178
|
() => startCancel(`timed out after ${timeoutMs}ms`),
|
|
2138
2179
|
timeoutMs
|
|
2139
2180
|
);
|
|
2140
|
-
worker.on(
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2181
|
+
worker.on(
|
|
2182
|
+
"message",
|
|
2183
|
+
(msg) => {
|
|
2184
|
+
if (phase === "grace") {
|
|
2185
|
+
if (msg?.type === "done") {
|
|
2186
|
+
finish(
|
|
2187
|
+
() => reject(
|
|
2188
|
+
new TaskCancelledError(
|
|
2189
|
+
`Task "${taskCtx.job.name}" ${cancelReason} (task completed after the timeout)`
|
|
2190
|
+
)
|
|
2147
2191
|
)
|
|
2148
|
-
)
|
|
2149
|
-
)
|
|
2192
|
+
);
|
|
2193
|
+
} else if (msg?.type === "error") {
|
|
2194
|
+
finish(() => reject(new TaskCancelledError(msg.error?.message ?? cancelReason)));
|
|
2195
|
+
}
|
|
2196
|
+
return;
|
|
2197
|
+
}
|
|
2198
|
+
if (phase !== "running") return;
|
|
2199
|
+
if (msg?.type === "done") {
|
|
2200
|
+
finish(() => resolve(msg.result));
|
|
2150
2201
|
} else if (msg?.type === "error") {
|
|
2151
|
-
finish(() => reject(
|
|
2202
|
+
finish(() => reject(reviveError(msg.error)));
|
|
2203
|
+
} else if (msg?.type === "progress") {
|
|
2204
|
+
options.onProgress?.(msg.value);
|
|
2152
2205
|
}
|
|
2153
|
-
return;
|
|
2154
|
-
}
|
|
2155
|
-
if (phase !== "running") return;
|
|
2156
|
-
if (msg?.type === "done") {
|
|
2157
|
-
finish(() => resolve(msg.result));
|
|
2158
|
-
} else if (msg?.type === "error") {
|
|
2159
|
-
finish(() => reject(new Error(msg.message ?? "task worker error")));
|
|
2160
2206
|
}
|
|
2207
|
+
);
|
|
2208
|
+
worker.on("messageerror", () => {
|
|
2209
|
+
finish(
|
|
2210
|
+
() => reject(new Error(`Task "${taskCtx.job.name}" worker message could not be deserialized`))
|
|
2211
|
+
);
|
|
2161
2212
|
});
|
|
2162
2213
|
worker.on("error", (err) => {
|
|
2163
2214
|
finish(() => reject(err));
|
|
@@ -2187,6 +2238,8 @@ async function runTaskInWorker(options) {
|
|
|
2187
2238
|
}
|
|
2188
2239
|
|
|
2189
2240
|
// src/task/taskQueue.ts
|
|
2241
|
+
var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["done", "failed", "cancelled"]);
|
|
2242
|
+
var MAX_FINISHED_RECORDS = 1e3;
|
|
2190
2243
|
function createTaskQueue(deps) {
|
|
2191
2244
|
const { registry, rootDir, driver } = deps;
|
|
2192
2245
|
const registriesView = deps.registries ?? createEmptyTaskRegistriesView();
|
|
@@ -2195,6 +2248,25 @@ function createTaskQueue(deps) {
|
|
|
2195
2248
|
const schemaCache = /* @__PURE__ */ new Map();
|
|
2196
2249
|
let started = false;
|
|
2197
2250
|
let stopped = false;
|
|
2251
|
+
let finishedCount = 0;
|
|
2252
|
+
function transitionStatus(record, status) {
|
|
2253
|
+
const wasTerminal = TERMINAL_STATUSES.has(record.status);
|
|
2254
|
+
const nowTerminal = TERMINAL_STATUSES.has(status);
|
|
2255
|
+
if (!wasTerminal && nowTerminal) finishedCount++;
|
|
2256
|
+
else if (wasTerminal && !nowTerminal) finishedCount--;
|
|
2257
|
+
record.status = status;
|
|
2258
|
+
}
|
|
2259
|
+
function evictFinishedRecords() {
|
|
2260
|
+
let toEvict = finishedCount - MAX_FINISHED_RECORDS;
|
|
2261
|
+
for (const [id, job] of records) {
|
|
2262
|
+
if (toEvict <= 0) break;
|
|
2263
|
+
if (TERMINAL_STATUSES.has(job.status)) {
|
|
2264
|
+
records.delete(id);
|
|
2265
|
+
finishedCount--;
|
|
2266
|
+
toEvict--;
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2198
2270
|
const loadTaskModule = deps.loadTaskModule ?? (async (filePath) => await import(filePath));
|
|
2199
2271
|
const loadPayloadSchema = deps.loadPayloadSchema ?? (async (filePath) => {
|
|
2200
2272
|
const zodPath = path3.join(path3.dirname(filePath), "zod.js");
|
|
@@ -2253,7 +2325,8 @@ function createTaskQueue(deps) {
|
|
|
2253
2325
|
createdAt: Date.now()
|
|
2254
2326
|
};
|
|
2255
2327
|
record.attempts = job.attempt;
|
|
2256
|
-
record
|
|
2328
|
+
transitionStatus(record, "running");
|
|
2329
|
+
delete record.progress;
|
|
2257
2330
|
record.error = void 0;
|
|
2258
2331
|
records.set(job.id, record);
|
|
2259
2332
|
try {
|
|
@@ -2268,7 +2341,11 @@ function createTaskQueue(deps) {
|
|
|
2268
2341
|
},
|
|
2269
2342
|
registries: snapshotRegistries(),
|
|
2270
2343
|
timeoutMs: meta.timeoutMs,
|
|
2271
|
-
|
|
2344
|
+
graceMs: meta.graceMs,
|
|
2345
|
+
externalSignal: job.signal,
|
|
2346
|
+
onProgress: (value) => {
|
|
2347
|
+
if (record.status === "running") record.progress = value;
|
|
2348
|
+
}
|
|
2272
2349
|
});
|
|
2273
2350
|
} else {
|
|
2274
2351
|
let mod = moduleCache2.get(job.name);
|
|
@@ -2283,17 +2360,22 @@ function createTaskQueue(deps) {
|
|
|
2283
2360
|
signal: job.signal,
|
|
2284
2361
|
config: deps.config,
|
|
2285
2362
|
job: { id: job.id, name: job.name, attempt: job.attempt },
|
|
2286
|
-
registries: registriesView
|
|
2363
|
+
registries: registriesView,
|
|
2364
|
+
progress: (value) => {
|
|
2365
|
+
if (record.status === "running") record.progress = value;
|
|
2366
|
+
}
|
|
2287
2367
|
};
|
|
2288
2368
|
result = await mod.run(job.payload, taskCtx);
|
|
2289
2369
|
}
|
|
2290
|
-
record
|
|
2370
|
+
transitionStatus(record, "done");
|
|
2291
2371
|
record.result = result;
|
|
2372
|
+
evictFinishedRecords();
|
|
2292
2373
|
return result;
|
|
2293
2374
|
} catch (err) {
|
|
2294
2375
|
const cancelled = err instanceof TaskCancelledError || job.signal.aborted;
|
|
2295
|
-
record
|
|
2376
|
+
transitionStatus(record, cancelled ? "cancelled" : "failed");
|
|
2296
2377
|
record.error = err instanceof Error ? err.message : String(err);
|
|
2378
|
+
evictFinishedRecords();
|
|
2297
2379
|
if (deps.onFailed) {
|
|
2298
2380
|
void Promise.resolve().then(
|
|
2299
2381
|
() => deps.onFailed({
|
|
@@ -2388,7 +2470,8 @@ function createTaskQueue(deps) {
|
|
|
2388
2470
|
}
|
|
2389
2471
|
await driver.cancel(name, id);
|
|
2390
2472
|
const record = records.get(id);
|
|
2391
|
-
if (record) record
|
|
2473
|
+
if (record) transitionStatus(record, "cancelled");
|
|
2474
|
+
evictFinishedRecords();
|
|
2392
2475
|
},
|
|
2393
2476
|
async retry(name, id) {
|
|
2394
2477
|
if (!driver.retry) {
|
|
@@ -2398,7 +2481,7 @@ function createTaskQueue(deps) {
|
|
|
2398
2481
|
}
|
|
2399
2482
|
await driver.retry(name, id);
|
|
2400
2483
|
const record = records.get(id);
|
|
2401
|
-
if (record) record
|
|
2484
|
+
if (record) transitionStatus(record, "pending");
|
|
2402
2485
|
},
|
|
2403
2486
|
async start() {
|
|
2404
2487
|
if (stopped) {
|
|
@@ -2506,9 +2589,15 @@ import fs2 from "fs";
|
|
|
2506
2589
|
var TASK_PATTERNS = ["src/tasks/**/task.ts"];
|
|
2507
2590
|
var TASK_FILENAME = "task.ts";
|
|
2508
2591
|
var CRON_RE = /(?:^|\n)\s*cron:\s*['"`]([^'"`\n]+)['"`]/;
|
|
2509
|
-
var
|
|
2510
|
-
var
|
|
2511
|
-
var
|
|
2592
|
+
var NUM_LITERAL = "(\\d+(?:_\\d+)*)";
|
|
2593
|
+
var CONCURRENCY_RE = new RegExp(`(?:^|\\n)\\s*concurrency:\\s*${NUM_LITERAL}`);
|
|
2594
|
+
var RETRIES_RE = new RegExp(`(?:^|\\n)\\s*retries:\\s*${NUM_LITERAL}`);
|
|
2595
|
+
var TIMEOUT_MS_RE = new RegExp(`(?:^|\\n)\\s*timeoutMs:\\s*${NUM_LITERAL}`);
|
|
2596
|
+
var GRACE_MS_RE = new RegExp(`(?:^|\\n)\\s*graceMs:\\s*${NUM_LITERAL}`);
|
|
2597
|
+
function parseNumericLiteral(raw) {
|
|
2598
|
+
return Number(raw.replace(/_/g, ""));
|
|
2599
|
+
}
|
|
2600
|
+
var MIN_ISOLATED_TIMEOUT_MS = 6e4;
|
|
2512
2601
|
function filePathToTaskName(filePath) {
|
|
2513
2602
|
const normalized = filePath.replace(/\\/g, "/");
|
|
2514
2603
|
const match = normalized.match(/(?:^|\/)tasks\/(.+)\/task\.ts$/);
|
|
@@ -2546,10 +2635,20 @@ async function scanTasks(rootDir, patterns) {
|
|
|
2546
2635
|
const concurrency = CONCURRENCY_RE.exec(source)?.[1];
|
|
2547
2636
|
const retries = RETRIES_RE.exec(source)?.[1];
|
|
2548
2637
|
const timeoutMs = TIMEOUT_MS_RE.exec(source)?.[1];
|
|
2638
|
+
const graceMs = GRACE_MS_RE.exec(source)?.[1];
|
|
2549
2639
|
if (cron !== void 0) manifest.cron = cron;
|
|
2550
|
-
if (concurrency !== void 0) manifest.concurrency =
|
|
2551
|
-
if (retries !== void 0) manifest.retries =
|
|
2552
|
-
if (timeoutMs !== void 0)
|
|
2640
|
+
if (concurrency !== void 0) manifest.concurrency = parseNumericLiteral(concurrency);
|
|
2641
|
+
if (retries !== void 0) manifest.retries = parseNumericLiteral(retries);
|
|
2642
|
+
if (timeoutMs !== void 0) {
|
|
2643
|
+
const value = parseNumericLiteral(timeoutMs);
|
|
2644
|
+
if (value < MIN_ISOLATED_TIMEOUT_MS) {
|
|
2645
|
+
throw new Error(
|
|
2646
|
+
`[faapi] Task "${name}" timeoutMs ${value} is below the minimum ${MIN_ISOLATED_TIMEOUT_MS}ms (1 minute). Declaring a timeout means this is a long-running task that needs real cancellation \u2014 tasks finishing within a minute do not need one: remove timeoutMs to run in-process (implement your own deadline with Promise.race if needed), or raise it.`
|
|
2647
|
+
);
|
|
2648
|
+
}
|
|
2649
|
+
manifest.timeoutMs = value;
|
|
2650
|
+
}
|
|
2651
|
+
if (graceMs !== void 0) manifest.graceMs = parseNumericLiteral(graceMs);
|
|
2553
2652
|
tasks.push(manifest);
|
|
2554
2653
|
}
|
|
2555
2654
|
return tasks.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -6116,7 +6215,8 @@ function serializeTasks(tasks, dist = "dist") {
|
|
|
6116
6215
|
...t.cron !== void 0 ? { cron: t.cron } : {},
|
|
6117
6216
|
...t.concurrency !== void 0 ? { concurrency: t.concurrency } : {},
|
|
6118
6217
|
...t.retries !== void 0 ? { retries: t.retries } : {},
|
|
6119
|
-
...t.timeoutMs !== void 0 ? { timeoutMs: t.timeoutMs } : {}
|
|
6218
|
+
...t.timeoutMs !== void 0 ? { timeoutMs: t.timeoutMs } : {},
|
|
6219
|
+
...t.graceMs !== void 0 ? { graceMs: t.graceMs } : {}
|
|
6120
6220
|
}));
|
|
6121
6221
|
}
|
|
6122
6222
|
async function writeTasksModule(manifest, outputPath) {
|
|
@@ -6132,7 +6232,8 @@ function hydrateTasks(manifest) {
|
|
|
6132
6232
|
cron: t.cron ?? void 0,
|
|
6133
6233
|
concurrency: t.concurrency ?? void 0,
|
|
6134
6234
|
retries: t.retries ?? void 0,
|
|
6135
|
-
timeoutMs: t.timeoutMs ?? void 0
|
|
6235
|
+
timeoutMs: t.timeoutMs ?? void 0,
|
|
6236
|
+
graceMs: t.graceMs ?? void 0
|
|
6136
6237
|
}));
|
|
6137
6238
|
}
|
|
6138
6239
|
function collectTaskSchemaSources(tasks, rootDir) {
|