@faapi/faapi 3.0.0 → 3.2.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 +381 -250
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +287 -95
- package/dist/index.js +336 -219
- package/dist/index.js.map +1 -1
- package/dist/testing.js +334 -221
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1365,11 +1365,16 @@ function clearAgentRegistry() {
|
|
|
1365
1365
|
function getAgent(name) {
|
|
1366
1366
|
return registry2.get(name);
|
|
1367
1367
|
}
|
|
1368
|
+
function getAgentEntry(name) {
|
|
1369
|
+
return registry2.get(name);
|
|
1370
|
+
}
|
|
1368
1371
|
function listAgents() {
|
|
1369
|
-
|
|
1372
|
+
const merged = /* @__PURE__ */ new Map();
|
|
1373
|
+
for (const agent of registry2.values()) merged.set(agent.name, agent);
|
|
1374
|
+
return Array.from(merged.values());
|
|
1370
1375
|
}
|
|
1371
1376
|
function resolveAgentTools(name) {
|
|
1372
|
-
const agent =
|
|
1377
|
+
const agent = getAgent(name);
|
|
1373
1378
|
if (!agent) return [];
|
|
1374
1379
|
const result = /* @__PURE__ */ new Map();
|
|
1375
1380
|
if (agent.tools) {
|
|
@@ -1381,16 +1386,41 @@ function resolveAgentTools(name) {
|
|
|
1381
1386
|
return Array.from(result.values());
|
|
1382
1387
|
}
|
|
1383
1388
|
function resolveSubAgents(name) {
|
|
1384
|
-
const agent =
|
|
1389
|
+
const agent = getAgent(name);
|
|
1385
1390
|
if (!agent || !agent.agents) return [];
|
|
1386
1391
|
const result = [];
|
|
1387
1392
|
for (const agentName of agent.agents) {
|
|
1388
|
-
const subAgent =
|
|
1393
|
+
const subAgent = getAgent(agentName);
|
|
1389
1394
|
if (subAgent) result.push(subAgent);
|
|
1390
1395
|
}
|
|
1391
1396
|
return result;
|
|
1392
1397
|
}
|
|
1393
1398
|
|
|
1399
|
+
// src/injection/skillRegistry.ts
|
|
1400
|
+
var registry3 = /* @__PURE__ */ new Map();
|
|
1401
|
+
function hydrateSkillRegistry(skills) {
|
|
1402
|
+
const next = /* @__PURE__ */ new Map();
|
|
1403
|
+
for (const skill of skills) {
|
|
1404
|
+
next.set(skill.name, skill);
|
|
1405
|
+
}
|
|
1406
|
+
registry3 = next;
|
|
1407
|
+
}
|
|
1408
|
+
function clearSkillRegistry() {
|
|
1409
|
+
registry3 = /* @__PURE__ */ new Map();
|
|
1410
|
+
}
|
|
1411
|
+
function upsertSkill(core) {
|
|
1412
|
+
registry3.set(core.name, core);
|
|
1413
|
+
}
|
|
1414
|
+
function removeSkill(name) {
|
|
1415
|
+
registry3.delete(name);
|
|
1416
|
+
}
|
|
1417
|
+
function getSkill(name) {
|
|
1418
|
+
return registry3.get(name);
|
|
1419
|
+
}
|
|
1420
|
+
function listSkills() {
|
|
1421
|
+
return Array.from(registry3.values());
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1394
1424
|
// src/loader/loadAgentModule.ts
|
|
1395
1425
|
import fs6 from "fs";
|
|
1396
1426
|
|
|
@@ -1692,12 +1722,29 @@ function isProductFresh(sourceAbsPath, productAbsPath) {
|
|
|
1692
1722
|
return false;
|
|
1693
1723
|
}
|
|
1694
1724
|
}
|
|
1695
|
-
|
|
1725
|
+
function createDevOnDemandState() {
|
|
1726
|
+
return {
|
|
1727
|
+
enabled: false,
|
|
1728
|
+
distDir: void 0,
|
|
1729
|
+
compiledFiles: /* @__PURE__ */ new Set(),
|
|
1730
|
+
generatedSchemas: /* @__PURE__ */ new Set(),
|
|
1731
|
+
inFlightCompilations: /* @__PURE__ */ new Map(),
|
|
1732
|
+
inFlightSchemaGenerations: /* @__PURE__ */ new Map()
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
var state = createDevOnDemandState();
|
|
1696
1736
|
function clearCompiledFiles() {
|
|
1697
|
-
compiledFiles.clear();
|
|
1737
|
+
state.compiledFiles.clear();
|
|
1738
|
+
state.inFlightCompilations.clear();
|
|
1698
1739
|
}
|
|
1699
1740
|
async function ensureCompiled(sourceAbsPath, rootDir, dist) {
|
|
1700
|
-
|
|
1741
|
+
const inFlight = state.inFlightCompilations.get(sourceAbsPath);
|
|
1742
|
+
if (inFlight) {
|
|
1743
|
+
await inFlight.catch(() => {
|
|
1744
|
+
});
|
|
1745
|
+
return false;
|
|
1746
|
+
}
|
|
1747
|
+
if (state.compiledFiles.has(sourceAbsPath)) {
|
|
1701
1748
|
return false;
|
|
1702
1749
|
}
|
|
1703
1750
|
if (!fs5.existsSync(sourceAbsPath)) {
|
|
@@ -1705,17 +1752,25 @@ async function ensureCompiled(sourceAbsPath, rootDir, dist) {
|
|
|
1705
1752
|
}
|
|
1706
1753
|
const productPath = prodSourcePathToProductPath(sourceAbsPath, rootDir, dist);
|
|
1707
1754
|
if (productPath && isProductFresh(sourceAbsPath, productPath)) {
|
|
1708
|
-
compiledFiles.add(sourceAbsPath);
|
|
1755
|
+
state.compiledFiles.add(sourceAbsPath);
|
|
1709
1756
|
return false;
|
|
1710
1757
|
}
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1758
|
+
const compilePromise = (async () => {
|
|
1759
|
+
await compileDevRoutes({
|
|
1760
|
+
rootDir,
|
|
1761
|
+
dist,
|
|
1762
|
+
files: [sourceAbsPath],
|
|
1763
|
+
logLevel: "silent"
|
|
1764
|
+
});
|
|
1765
|
+
state.compiledFiles.add(sourceAbsPath);
|
|
1766
|
+
})();
|
|
1767
|
+
state.inFlightCompilations.set(sourceAbsPath, compilePromise);
|
|
1768
|
+
try {
|
|
1769
|
+
await compilePromise;
|
|
1770
|
+
return true;
|
|
1771
|
+
} finally {
|
|
1772
|
+
state.inFlightCompilations.delete(sourceAbsPath);
|
|
1773
|
+
}
|
|
1719
1774
|
}
|
|
1720
1775
|
function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
|
|
1721
1776
|
const rel = path6.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
@@ -1724,12 +1779,18 @@ function prodSourcePathToProductPath(sourceAbsPath, rootDir, dist) {
|
|
|
1724
1779
|
const jsRel = relWithoutSrc.replace(/\.ts$/, ".js");
|
|
1725
1780
|
return path6.resolve(rootDir, dist, jsRel);
|
|
1726
1781
|
}
|
|
1727
|
-
var generatedSchemas = /* @__PURE__ */ new Set();
|
|
1728
1782
|
function clearGeneratedSchemas() {
|
|
1729
|
-
generatedSchemas.clear();
|
|
1783
|
+
state.generatedSchemas.clear();
|
|
1784
|
+
state.inFlightSchemaGenerations.clear();
|
|
1730
1785
|
}
|
|
1731
1786
|
async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir, dist) {
|
|
1732
|
-
|
|
1787
|
+
const inFlight = state.inFlightSchemaGenerations.get(schemaPath);
|
|
1788
|
+
if (inFlight) {
|
|
1789
|
+
await inFlight.catch(() => {
|
|
1790
|
+
});
|
|
1791
|
+
return false;
|
|
1792
|
+
}
|
|
1793
|
+
if (state.generatedSchemas.has(schemaPath)) {
|
|
1733
1794
|
return false;
|
|
1734
1795
|
}
|
|
1735
1796
|
const prodAbsPath = path6.resolve(rootDir, routeFilePath);
|
|
@@ -1738,7 +1799,7 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
|
|
|
1738
1799
|
return false;
|
|
1739
1800
|
}
|
|
1740
1801
|
if (isProductFresh(sourceAbsPath, schemaPath)) {
|
|
1741
|
-
generatedSchemas.add(schemaPath);
|
|
1802
|
+
state.generatedSchemas.add(schemaPath);
|
|
1742
1803
|
return false;
|
|
1743
1804
|
}
|
|
1744
1805
|
const fileRoutes = routes.filter((r) => r.filePath === routeFilePath);
|
|
@@ -1747,9 +1808,17 @@ async function ensureSchemaGenerated(schemaPath, routeFilePath, routes, rootDir,
|
|
|
1747
1808
|
}
|
|
1748
1809
|
const sourceRelPath = path6.relative(rootDir, sourceAbsPath).replace(/\\/g, "/");
|
|
1749
1810
|
const sourceRoutes = fileRoutes.map((r) => ({ ...r, filePath: sourceRelPath }));
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1811
|
+
const generatePromise = (async () => {
|
|
1812
|
+
await generateSchemaFiles(sourceRoutes, rootDir, dist);
|
|
1813
|
+
state.generatedSchemas.add(schemaPath);
|
|
1814
|
+
})();
|
|
1815
|
+
state.inFlightSchemaGenerations.set(schemaPath, generatePromise);
|
|
1816
|
+
try {
|
|
1817
|
+
await generatePromise;
|
|
1818
|
+
return true;
|
|
1819
|
+
} finally {
|
|
1820
|
+
state.inFlightSchemaGenerations.delete(schemaPath);
|
|
1821
|
+
}
|
|
1753
1822
|
}
|
|
1754
1823
|
async function deleteSchemaFiles(routes, rootDir, dist) {
|
|
1755
1824
|
const deleted = /* @__PURE__ */ new Set();
|
|
@@ -1775,17 +1844,15 @@ function prodPathToSourcePath(prodAbsPath, rootDir, dist) {
|
|
|
1775
1844
|
if (fs5.existsSync(tsAbs)) return tsAbs;
|
|
1776
1845
|
return path6.resolve(rootDir, srcRel);
|
|
1777
1846
|
}
|
|
1778
|
-
var devOnDemandEnabled = false;
|
|
1779
1847
|
function isDevOnDemandEnabled() {
|
|
1780
|
-
return
|
|
1848
|
+
return state.enabled;
|
|
1781
1849
|
}
|
|
1782
|
-
var devDistDir;
|
|
1783
1850
|
function getDevDist() {
|
|
1784
|
-
return
|
|
1851
|
+
return state.distDir;
|
|
1785
1852
|
}
|
|
1786
1853
|
|
|
1787
1854
|
// src/loader/loadAgentModule.ts
|
|
1788
|
-
async function loadAgentModule(filePath,
|
|
1855
|
+
async function loadAgentModule(filePath, hasRun, rootDir) {
|
|
1789
1856
|
if (isDevOnDemandEnabled() && rootDir) {
|
|
1790
1857
|
const dist = getDevDist();
|
|
1791
1858
|
if (dist) {
|
|
@@ -1809,30 +1876,6 @@ async function loadAgentModule(filePath, hasConfig, hasRun, rootDir) {
|
|
|
1809
1876
|
const reason = err instanceof Error ? err.message : String(err);
|
|
1810
1877
|
throw new Error(`Failed to load agent module "${filePath}": ${reason}`, { cause: err });
|
|
1811
1878
|
}
|
|
1812
|
-
let config;
|
|
1813
|
-
if (hasConfig) {
|
|
1814
|
-
const configExport = resolveExport(module, "config");
|
|
1815
|
-
if (configExport === void 0) {
|
|
1816
|
-
throw new Error(
|
|
1817
|
-
`Agent module "${filePath}" does not export "config" (hasConfig=true but export missing).`
|
|
1818
|
-
);
|
|
1819
|
-
}
|
|
1820
|
-
if (typeof configExport === "function") {
|
|
1821
|
-
const returned = configExport();
|
|
1822
|
-
if (returned === null || typeof returned !== "object") {
|
|
1823
|
-
throw new Error(
|
|
1824
|
-
`Agent module "${filePath}" config() did not return an object (got ${returned === null ? "null" : typeof returned}).`
|
|
1825
|
-
);
|
|
1826
|
-
}
|
|
1827
|
-
config = returned;
|
|
1828
|
-
} else if (typeof configExport === "object" && configExport !== null) {
|
|
1829
|
-
config = configExport;
|
|
1830
|
-
} else {
|
|
1831
|
-
throw new Error(
|
|
1832
|
-
`Agent module "${filePath}" config export must be an object or function, got ${typeof configExport}.`
|
|
1833
|
-
);
|
|
1834
|
-
}
|
|
1835
|
-
}
|
|
1836
1879
|
let run;
|
|
1837
1880
|
if (hasRun) {
|
|
1838
1881
|
const runExport = resolveExport(module, "run");
|
|
@@ -1843,7 +1886,7 @@ async function loadAgentModule(filePath, hasConfig, hasRun, rootDir) {
|
|
|
1843
1886
|
}
|
|
1844
1887
|
run = runExport;
|
|
1845
1888
|
}
|
|
1846
|
-
return {
|
|
1889
|
+
return { run };
|
|
1847
1890
|
}
|
|
1848
1891
|
|
|
1849
1892
|
// src/loader/loadToolModule.ts
|
|
@@ -2509,6 +2552,12 @@ var ModuleLoadError = class extends FaapiError {
|
|
|
2509
2552
|
this.name = "ModuleLoadError";
|
|
2510
2553
|
}
|
|
2511
2554
|
};
|
|
2555
|
+
var PayloadTooLargeError = class extends FaapiError {
|
|
2556
|
+
constructor(maxSize) {
|
|
2557
|
+
super("PAYLOAD_TOO_LARGE", `Request body exceeds size limit of ${maxSize} bytes`, 413);
|
|
2558
|
+
this.name = "PayloadTooLargeError";
|
|
2559
|
+
}
|
|
2560
|
+
};
|
|
2512
2561
|
|
|
2513
2562
|
// src/cli/createAppCore.ts
|
|
2514
2563
|
import fs15 from "fs";
|
|
@@ -2795,6 +2844,92 @@ function createSseWriter() {
|
|
|
2795
2844
|
return writer;
|
|
2796
2845
|
}
|
|
2797
2846
|
|
|
2847
|
+
// src/response/responseFormatter.ts
|
|
2848
|
+
function defaultOk(data) {
|
|
2849
|
+
return { data };
|
|
2850
|
+
}
|
|
2851
|
+
function defaultFail(e) {
|
|
2852
|
+
const error = { message: e.message };
|
|
2853
|
+
if (e.code !== void 0) error.code = e.code;
|
|
2854
|
+
return { error };
|
|
2855
|
+
}
|
|
2856
|
+
function getResponseConfig(config) {
|
|
2857
|
+
return config?.response;
|
|
2858
|
+
}
|
|
2859
|
+
function resolveOkFn(config) {
|
|
2860
|
+
return getResponseConfig(config)?.ok ?? defaultOk;
|
|
2861
|
+
}
|
|
2862
|
+
function resolveFailFn(config) {
|
|
2863
|
+
return getResponseConfig(config)?.fail ?? defaultFail;
|
|
2864
|
+
}
|
|
2865
|
+
function jsonOk(body, status = 200, extraHeaders) {
|
|
2866
|
+
return jsonRaw(body, status, extraHeaders);
|
|
2867
|
+
}
|
|
2868
|
+
function jsonRaw(body, status, extraHeaders) {
|
|
2869
|
+
const headers = new Headers({ "Content-Type": "application/json" });
|
|
2870
|
+
if (extraHeaders) {
|
|
2871
|
+
const extra = new Headers(extraHeaders);
|
|
2872
|
+
extra.forEach((value, key) => headers.set(key, value));
|
|
2873
|
+
}
|
|
2874
|
+
return new Response(JSON.stringify(body), { status, headers });
|
|
2875
|
+
}
|
|
2876
|
+
function wrapOkResult(result, config) {
|
|
2877
|
+
if (result instanceof Response) return result;
|
|
2878
|
+
return resolveOkFn(config)(result);
|
|
2879
|
+
}
|
|
2880
|
+
function formatFailResponse(options, config) {
|
|
2881
|
+
const failFn = resolveFailFn(config);
|
|
2882
|
+
const body = failFn({
|
|
2883
|
+
status: options.status,
|
|
2884
|
+
code: options.code,
|
|
2885
|
+
message: options.message
|
|
2886
|
+
});
|
|
2887
|
+
return jsonOk(body, options.status ?? 500);
|
|
2888
|
+
}
|
|
2889
|
+
function formatErrorResponse(error, config) {
|
|
2890
|
+
const failFn = resolveFailFn(config);
|
|
2891
|
+
if (error instanceof ValidationError) {
|
|
2892
|
+
const body2 = failFn({
|
|
2893
|
+
status: error.statusCode,
|
|
2894
|
+
code: error.code,
|
|
2895
|
+
message: error.message
|
|
2896
|
+
});
|
|
2897
|
+
const bodyObj = typeof body2 === "object" && body2 !== null ? body2 : { error: body2 };
|
|
2898
|
+
const errorObj = bodyObj.error ?? bodyObj;
|
|
2899
|
+
if (errorObj) {
|
|
2900
|
+
errorObj.issues = error.issues;
|
|
2901
|
+
}
|
|
2902
|
+
return jsonOk(bodyObj, error.statusCode);
|
|
2903
|
+
}
|
|
2904
|
+
if (error instanceof MethodNotAllowedError) {
|
|
2905
|
+
const body2 = failFn({
|
|
2906
|
+
status: error.statusCode,
|
|
2907
|
+
code: error.code,
|
|
2908
|
+
message: error.message
|
|
2909
|
+
});
|
|
2910
|
+
return jsonOk(body2, error.statusCode, { Allow: error.allowedMethods.join(", ") });
|
|
2911
|
+
}
|
|
2912
|
+
if (error instanceof PayloadTooLargeError) {
|
|
2913
|
+
const body2 = failFn({
|
|
2914
|
+
status: error.statusCode,
|
|
2915
|
+
code: error.code,
|
|
2916
|
+
message: error.message
|
|
2917
|
+
});
|
|
2918
|
+
return jsonOk(body2, error.statusCode);
|
|
2919
|
+
}
|
|
2920
|
+
if (error instanceof FaapiError) {
|
|
2921
|
+
const body2 = failFn({
|
|
2922
|
+
status: error.statusCode,
|
|
2923
|
+
code: error.code,
|
|
2924
|
+
message: error.message
|
|
2925
|
+
});
|
|
2926
|
+
return jsonOk(body2, error.statusCode);
|
|
2927
|
+
}
|
|
2928
|
+
const message = error instanceof Error ? error.message : "An unknown error occurred";
|
|
2929
|
+
const body = failFn({ status: 500, code: "INTERNAL_ERROR", message });
|
|
2930
|
+
return jsonOk(body, 500);
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2798
2933
|
// src/runtime/createContext.ts
|
|
2799
2934
|
function parseCookies(cookieHeader) {
|
|
2800
2935
|
const cookies = /* @__PURE__ */ new Map();
|
|
@@ -2855,11 +2990,7 @@ function createContext(request, params, config = {}, ip = "") {
|
|
|
2855
2990
|
});
|
|
2856
2991
|
},
|
|
2857
2992
|
json(data, status) {
|
|
2858
|
-
|
|
2859
|
-
return new Response(JSON.stringify(data), {
|
|
2860
|
-
status: status ?? 200,
|
|
2861
|
-
headers
|
|
2862
|
-
});
|
|
2993
|
+
return jsonOk(data, status ?? 200);
|
|
2863
2994
|
},
|
|
2864
2995
|
html(html, status) {
|
|
2865
2996
|
const headers = { "Content-Type": "text/html; charset=utf-8" };
|
|
@@ -2895,18 +3026,22 @@ function createContext(request, params, config = {}, ip = "") {
|
|
|
2895
3026
|
/**
|
|
2896
3027
|
* 显式包装成功响应(返回 Response,不会被自动包裹再次包装)
|
|
2897
3028
|
*
|
|
3029
|
+
* 实现委托给 [responseFormatter.wrapOkResult](../response/responseFormatter.ts),
|
|
3030
|
+
* 与 handler `return data` 走的自动包裹路径共享同一套 ok 函数。
|
|
3031
|
+
*
|
|
2898
3032
|
* 用 config.response.ok(或默认 (data) => ({ data })) 包裹 data 并返回 JSON Response。
|
|
2899
3033
|
* handler 也可直接 return data,框架会自动用 ok 包裹,两者等价。
|
|
2900
3034
|
*/
|
|
2901
3035
|
ok(data) {
|
|
2902
|
-
const
|
|
2903
|
-
|
|
2904
|
-
const body = okFn(data);
|
|
2905
|
-
return ctx.json(body);
|
|
3036
|
+
const body = wrapOkResult(data, config);
|
|
3037
|
+
return jsonOk(body, 200);
|
|
2906
3038
|
},
|
|
2907
3039
|
/**
|
|
2908
3040
|
* 返回错误响应(对象形式参数,status 和 code 均可省略)
|
|
2909
3041
|
*
|
|
3042
|
+
* 实现委托给 [responseFormatter.formatFailResponse](../response/responseFormatter.ts),
|
|
3043
|
+
* 与 formatErrorResponse(handler 抛错兜底)共享同一套 fail 函数,确保错误格式一致。
|
|
3044
|
+
*
|
|
2910
3045
|
* - status 省略时 HTTP 状态码默认 500
|
|
2911
3046
|
* - code 省略时响应 body 里不含 code 字段(默认 fail 函数只放非 undefined 的字段)
|
|
2912
3047
|
* - status 和 code 独立无关联
|
|
@@ -2914,18 +3049,7 @@ function createContext(request, params, config = {}, ip = "") {
|
|
|
2914
3049
|
* body 用 config.response.fail(或默认实现)包装。
|
|
2915
3050
|
*/
|
|
2916
3051
|
fail(options) {
|
|
2917
|
-
|
|
2918
|
-
const failFn = responseConfig?.fail ?? ((e) => {
|
|
2919
|
-
const error = { message: e.message };
|
|
2920
|
-
if (e.code !== void 0) error.code = e.code;
|
|
2921
|
-
return { error };
|
|
2922
|
-
});
|
|
2923
|
-
const body = failFn({
|
|
2924
|
-
status: options.status,
|
|
2925
|
-
code: options.code,
|
|
2926
|
-
message: options.message
|
|
2927
|
-
});
|
|
2928
|
-
return ctx.json(body, options.status ?? 500);
|
|
3052
|
+
return formatFailResponse(options, config);
|
|
2929
3053
|
}
|
|
2930
3054
|
};
|
|
2931
3055
|
const extend = config?.extendContext;
|
|
@@ -3191,10 +3315,7 @@ async function injectParamsAsync(handler, ctx, body, injectors) {
|
|
|
3191
3315
|
|
|
3192
3316
|
// src/runtime/invokeHandler.ts
|
|
3193
3317
|
function wrapResult(result, ctx) {
|
|
3194
|
-
|
|
3195
|
-
const responseConfig = ctx.config.response;
|
|
3196
|
-
const okFn = responseConfig?.ok ?? ((d) => ({ data: d }));
|
|
3197
|
-
return okFn(result);
|
|
3318
|
+
return wrapOkResult(result, ctx.config);
|
|
3198
3319
|
}
|
|
3199
3320
|
function mergeMeta(response, meta) {
|
|
3200
3321
|
const hasMeta = meta.status !== void 0 || Object.keys(meta.headers).length > 0 || meta.setCookies.length > 0;
|
|
@@ -3417,52 +3538,6 @@ import fs12 from "fs";
|
|
|
3417
3538
|
import { WebSocketServer, WebSocket } from "ws";
|
|
3418
3539
|
import path10 from "path";
|
|
3419
3540
|
|
|
3420
|
-
// src/errors/formatErrorResponse.ts
|
|
3421
|
-
function formatErrorResponse(error) {
|
|
3422
|
-
if (error instanceof ValidationError) {
|
|
3423
|
-
const body2 = {
|
|
3424
|
-
code: error.code,
|
|
3425
|
-
message: error.message,
|
|
3426
|
-
issues: error.issues
|
|
3427
|
-
};
|
|
3428
|
-
return new Response(JSON.stringify({ error: body2 }), {
|
|
3429
|
-
status: error.statusCode,
|
|
3430
|
-
headers: { "Content-Type": "application/json" }
|
|
3431
|
-
});
|
|
3432
|
-
}
|
|
3433
|
-
if (error instanceof MethodNotAllowedError) {
|
|
3434
|
-
const body2 = {
|
|
3435
|
-
code: error.code,
|
|
3436
|
-
message: error.message
|
|
3437
|
-
};
|
|
3438
|
-
return new Response(JSON.stringify({ error: body2 }), {
|
|
3439
|
-
status: error.statusCode,
|
|
3440
|
-
headers: {
|
|
3441
|
-
"Content-Type": "application/json",
|
|
3442
|
-
Allow: error.allowedMethods.join(", ")
|
|
3443
|
-
}
|
|
3444
|
-
});
|
|
3445
|
-
}
|
|
3446
|
-
if (error instanceof FaapiError) {
|
|
3447
|
-
const body2 = {
|
|
3448
|
-
code: error.code,
|
|
3449
|
-
message: error.message
|
|
3450
|
-
};
|
|
3451
|
-
return new Response(JSON.stringify({ error: body2 }), {
|
|
3452
|
-
status: error.statusCode,
|
|
3453
|
-
headers: { "Content-Type": "application/json" }
|
|
3454
|
-
});
|
|
3455
|
-
}
|
|
3456
|
-
const body = {
|
|
3457
|
-
code: "INTERNAL_ERROR",
|
|
3458
|
-
message: error instanceof Error ? error.message : "An unknown error occurred"
|
|
3459
|
-
};
|
|
3460
|
-
return new Response(JSON.stringify({ error: body }), {
|
|
3461
|
-
status: 500,
|
|
3462
|
-
headers: { "Content-Type": "application/json" }
|
|
3463
|
-
});
|
|
3464
|
-
}
|
|
3465
|
-
|
|
3466
3541
|
// src/server/serverUtils.ts
|
|
3467
3542
|
function nodeHttpToWebHeaders(req) {
|
|
3468
3543
|
const headers = new Headers();
|
|
@@ -3476,9 +3551,9 @@ function nodeHttpToWebHeaders(req) {
|
|
|
3476
3551
|
}
|
|
3477
3552
|
return headers;
|
|
3478
3553
|
}
|
|
3479
|
-
function buildErrorResponse(err) {
|
|
3554
|
+
function buildErrorResponse(err, config) {
|
|
3480
3555
|
try {
|
|
3481
|
-
return formatErrorResponse(err);
|
|
3556
|
+
return formatErrorResponse(err, config);
|
|
3482
3557
|
} catch {
|
|
3483
3558
|
return new Response(
|
|
3484
3559
|
JSON.stringify({ error: { code: "INTERNAL_ERROR", message: "Internal Server Error" } }),
|
|
@@ -3502,37 +3577,42 @@ function setCachedMiddlewares(absPath, bundle) {
|
|
|
3502
3577
|
middlewareCache.set(absPath, bundle);
|
|
3503
3578
|
}
|
|
3504
3579
|
async function loadMiddlewaresFile(filePath) {
|
|
3580
|
+
let module;
|
|
3505
3581
|
try {
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
return true;
|
|
3518
|
-
});
|
|
3519
|
-
const injectors = module.injectors ?? {};
|
|
3520
|
-
if (typeof injectors !== "object" || injectors === null) {
|
|
3521
|
-
console.warn(`[faapi] injectors \u5E94\u5BFC\u51FA\u5BF9\u8C61\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
|
|
3522
|
-
return { middlewares: validMiddlewares, injectors: {} };
|
|
3523
|
-
}
|
|
3524
|
-
const validInjectors = {};
|
|
3525
|
-
for (const [name, injector] of Object.entries(injectors)) {
|
|
3526
|
-
if (typeof injector !== "function") {
|
|
3527
|
-
console.warn(`[faapi] \u6CE8\u5165\u5668 ${name} \u5E94\u4E3A\u51FD\u6570\uFF0C\u5DF2\u5FFD\u7565`);
|
|
3528
|
-
continue;
|
|
3529
|
-
}
|
|
3530
|
-
validInjectors[name] = injector;
|
|
3531
|
-
}
|
|
3532
|
-
return { middlewares: validMiddlewares, injectors: validInjectors };
|
|
3533
|
-
} catch {
|
|
3582
|
+
module = await importWithCacheBust(filePath);
|
|
3583
|
+
} catch (err) {
|
|
3584
|
+
console.error(
|
|
3585
|
+
`[faapi] Failed to load middlewares from ${filePath}:`,
|
|
3586
|
+
err instanceof Error ? err.stack ?? err.message : err
|
|
3587
|
+
);
|
|
3588
|
+
return { middlewares: [], injectors: {} };
|
|
3589
|
+
}
|
|
3590
|
+
const middlewares = module.default ?? module.middlewares ?? [];
|
|
3591
|
+
if (!Array.isArray(middlewares)) {
|
|
3592
|
+
console.warn(`[faapi] middlewares.ts \u5E94\u5BFC\u51FA\u6570\u7EC4\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
|
|
3534
3593
|
return { middlewares: [], injectors: {} };
|
|
3535
3594
|
}
|
|
3595
|
+
const validMiddlewares = middlewares.filter((m) => {
|
|
3596
|
+
if (typeof m !== "function") {
|
|
3597
|
+
console.warn(`[faapi] \u65E0\u6548\u7684\u4E2D\u95F4\u4EF6\u9879\uFF08\u5E94\u4E3A\u51FD\u6570\uFF09\uFF0C\u5DF2\u5FFD\u7565: ${typeof m}`);
|
|
3598
|
+
return false;
|
|
3599
|
+
}
|
|
3600
|
+
return true;
|
|
3601
|
+
});
|
|
3602
|
+
const injectors = module.injectors ?? {};
|
|
3603
|
+
if (typeof injectors !== "object" || injectors === null) {
|
|
3604
|
+
console.warn(`[faapi] injectors \u5E94\u5BFC\u51FA\u5BF9\u8C61\uFF0C\u5DF2\u5FFD\u7565: ${filePath}`);
|
|
3605
|
+
return { middlewares: validMiddlewares, injectors: {} };
|
|
3606
|
+
}
|
|
3607
|
+
const validInjectors = {};
|
|
3608
|
+
for (const [name, injector] of Object.entries(injectors)) {
|
|
3609
|
+
if (typeof injector !== "function") {
|
|
3610
|
+
console.warn(`[faapi] \u6CE8\u5165\u5668 ${name} \u5E94\u4E3A\u51FD\u6570\uFF0C\u5DF2\u5FFD\u7565`);
|
|
3611
|
+
continue;
|
|
3612
|
+
}
|
|
3613
|
+
validInjectors[name] = injector;
|
|
3614
|
+
}
|
|
3615
|
+
return { middlewares: validMiddlewares, injectors: validInjectors };
|
|
3536
3616
|
}
|
|
3537
3617
|
async function loadMergedMiddlewares(middlewarePaths) {
|
|
3538
3618
|
if (middlewarePaths.length === 0) return void 0;
|
|
@@ -3739,25 +3819,51 @@ function toWebRequest(req, bodyLimit = DEFAULT_BODY_LIMIT) {
|
|
|
3739
3819
|
}
|
|
3740
3820
|
function limitStreamSize(stream, maxSize) {
|
|
3741
3821
|
let totalSize = 0;
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
controller.close();
|
|
3822
|
+
let reader;
|
|
3823
|
+
let errored = false;
|
|
3824
|
+
const releaseReader = () => {
|
|
3825
|
+
if (reader) {
|
|
3826
|
+
try {
|
|
3748
3827
|
reader.releaseLock();
|
|
3749
|
-
|
|
3828
|
+
} catch {
|
|
3750
3829
|
}
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3830
|
+
reader = void 0;
|
|
3831
|
+
}
|
|
3832
|
+
};
|
|
3833
|
+
const failStream = (controller, err) => {
|
|
3834
|
+
if (errored) return;
|
|
3835
|
+
errored = true;
|
|
3836
|
+
controller.error(err instanceof Error ? err : new Error(String(err)));
|
|
3837
|
+
releaseReader();
|
|
3838
|
+
};
|
|
3839
|
+
return new ReadableStream({
|
|
3840
|
+
async pull(controller) {
|
|
3841
|
+
if (!reader) reader = stream.getReader();
|
|
3842
|
+
try {
|
|
3843
|
+
const { done, value } = await reader.read();
|
|
3844
|
+
if (done) {
|
|
3845
|
+
controller.close();
|
|
3846
|
+
releaseReader();
|
|
3847
|
+
return;
|
|
3848
|
+
}
|
|
3849
|
+
totalSize += value.byteLength;
|
|
3850
|
+
if (totalSize > maxSize) {
|
|
3851
|
+
failStream(controller, new PayloadTooLargeError(maxSize));
|
|
3852
|
+
return;
|
|
3853
|
+
}
|
|
3854
|
+
controller.enqueue(value);
|
|
3855
|
+
} catch (err) {
|
|
3856
|
+
failStream(controller, err);
|
|
3756
3857
|
}
|
|
3757
|
-
controller.enqueue(value);
|
|
3758
3858
|
},
|
|
3759
3859
|
cancel(reason) {
|
|
3760
|
-
reader
|
|
3860
|
+
if (reader) {
|
|
3861
|
+
try {
|
|
3862
|
+
reader.cancel(reason);
|
|
3863
|
+
} catch {
|
|
3864
|
+
}
|
|
3865
|
+
releaseReader();
|
|
3866
|
+
}
|
|
3761
3867
|
}
|
|
3762
3868
|
});
|
|
3763
3869
|
}
|
|
@@ -3838,21 +3944,27 @@ function createServer(options) {
|
|
|
3838
3944
|
}
|
|
3839
3945
|
return { server, routesRef };
|
|
3840
3946
|
}
|
|
3841
|
-
|
|
3947
|
+
function prepareRequest(req, config, bodyLimit) {
|
|
3842
3948
|
const request = toWebRequest(req, bodyLimit);
|
|
3843
3949
|
const method = request.method.toUpperCase();
|
|
3844
3950
|
const urlPath = new URL(request.url).pathname;
|
|
3845
3951
|
const ctx = createContext(request, {}, config, getClientIp(req));
|
|
3846
3952
|
const meta = ctx.meta;
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3953
|
+
return { request, ctx, meta, method, urlPath };
|
|
3954
|
+
}
|
|
3955
|
+
function resolveRouteOrThrow(routes, method, urlPath) {
|
|
3956
|
+
const match = matchRoute(routes, method, urlPath);
|
|
3957
|
+
if (match) return match;
|
|
3958
|
+
const allowedMethods = findAllowedMethods(routes, urlPath);
|
|
3959
|
+
if (allowedMethods.length > 0) {
|
|
3960
|
+
throw new MethodNotAllowedError(method, urlPath, allowedMethods);
|
|
3961
|
+
}
|
|
3962
|
+
throw new RouteNotFoundError(urlPath);
|
|
3963
|
+
}
|
|
3964
|
+
function createRoutePipeline(opts) {
|
|
3965
|
+
const { routes, method, urlPath, ctx, request, rootDir, dist, globalInjectors } = opts;
|
|
3966
|
+
return async () => {
|
|
3967
|
+
const match = resolveRouteOrThrow(routes, method, urlPath);
|
|
3856
3968
|
ctx.params = match.params;
|
|
3857
3969
|
const { route } = match;
|
|
3858
3970
|
const absoluteFilePath = path11.resolve(rootDir, route.filePath);
|
|
@@ -3882,37 +3994,44 @@ async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares,
|
|
|
3882
3994
|
}
|
|
3883
3995
|
}
|
|
3884
3996
|
const mergedInjectors = globalInjectors ? { ...globalInjectors, ...route.injectors } : route.injectors;
|
|
3885
|
-
|
|
3886
|
-
routeModule.handler,
|
|
3887
|
-
ctx,
|
|
3888
|
-
body,
|
|
3889
|
-
route.middlewares,
|
|
3890
|
-
mergedInjectors
|
|
3891
|
-
);
|
|
3892
|
-
return response;
|
|
3997
|
+
return await invokeHandler(routeModule.handler, ctx, body, route.middlewares, mergedInjectors);
|
|
3893
3998
|
};
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
}
|
|
3904
|
-
response = await routePipeline();
|
|
3999
|
+
}
|
|
4000
|
+
async function sendSuccessResponse(response, res) {
|
|
4001
|
+
await sendNodeResponse(response, res);
|
|
4002
|
+
}
|
|
4003
|
+
async function sendErrorResponse(err, meta, res, onError, ctx) {
|
|
4004
|
+
await sendNodeResponse(mergeMeta(buildErrorResponse(err, ctx.config), meta), res);
|
|
4005
|
+
if (onError) {
|
|
4006
|
+
try {
|
|
4007
|
+
await onError(err, ctx);
|
|
4008
|
+
} catch {
|
|
3905
4009
|
}
|
|
3906
|
-
|
|
4010
|
+
}
|
|
4011
|
+
}
|
|
4012
|
+
async function handleRequest(routes, rootDir, dist, req, res, configMiddlewares, onError, config, globalMiddlewares, globalInjectors, bodyLimit) {
|
|
4013
|
+
const { request, ctx, meta, method, urlPath } = prepareRequest(req, config, bodyLimit);
|
|
4014
|
+
const routePipeline = createRoutePipeline({
|
|
4015
|
+
routes,
|
|
4016
|
+
method,
|
|
4017
|
+
urlPath,
|
|
4018
|
+
ctx,
|
|
4019
|
+
request,
|
|
4020
|
+
rootDir,
|
|
4021
|
+
dist,
|
|
4022
|
+
globalMiddlewares,
|
|
4023
|
+
globalInjectors
|
|
4024
|
+
});
|
|
4025
|
+
const outerMiddlewares = [];
|
|
4026
|
+
if (configMiddlewares.length > 0) outerMiddlewares.push(...configMiddlewares);
|
|
4027
|
+
if (globalMiddlewares && globalMiddlewares.length > 0) {
|
|
4028
|
+
outerMiddlewares.push(...globalMiddlewares);
|
|
4029
|
+
}
|
|
4030
|
+
try {
|
|
4031
|
+
const response = outerMiddlewares.length > 0 ? await compose(outerMiddlewares, ctx, routePipeline) : await routePipeline();
|
|
4032
|
+
await sendSuccessResponse(response, res);
|
|
3907
4033
|
} catch (err) {
|
|
3908
|
-
|
|
3909
|
-
await sendNodeResponse(mergeMeta(errorResponse, meta), res);
|
|
3910
|
-
if (onError) {
|
|
3911
|
-
try {
|
|
3912
|
-
await onError(err, ctx);
|
|
3913
|
-
} catch {
|
|
3914
|
-
}
|
|
3915
|
-
}
|
|
4034
|
+
await sendErrorResponse(err, meta, res, onError, ctx);
|
|
3916
4035
|
}
|
|
3917
4036
|
}
|
|
3918
4037
|
|
|
@@ -4011,7 +4130,6 @@ function extractAgentMetadata(program, filePath, pathMeta) {
|
|
|
4011
4130
|
name: agentNameOverride ?? pathMeta.name,
|
|
4012
4131
|
description,
|
|
4013
4132
|
filePath: pathMeta.filePath,
|
|
4014
|
-
hasConfig: pathMeta.hasConfig,
|
|
4015
4133
|
hasRun: pathMeta.hasRun,
|
|
4016
4134
|
systemPrompt,
|
|
4017
4135
|
tools,
|
|
@@ -4177,7 +4295,6 @@ function serializeAgents(agents, dist = "dist") {
|
|
|
4177
4295
|
return agents.map((a) => ({
|
|
4178
4296
|
name: a.name,
|
|
4179
4297
|
description: a.description,
|
|
4180
|
-
hasConfig: a.hasConfig,
|
|
4181
4298
|
hasRun: a.hasRun,
|
|
4182
4299
|
systemPrompt: a.systemPrompt,
|
|
4183
4300
|
tools: a.tools,
|
|
@@ -4200,7 +4317,6 @@ function hydrateAgents(manifest) {
|
|
|
4200
4317
|
name: a.name,
|
|
4201
4318
|
description: a.description ?? void 0,
|
|
4202
4319
|
filePath: a.filePath,
|
|
4203
|
-
hasConfig: a.hasConfig,
|
|
4204
4320
|
hasRun: a.hasRun,
|
|
4205
4321
|
systemPrompt: a.systemPrompt ?? void 0,
|
|
4206
4322
|
tools: a.tools ?? void 0,
|
|
@@ -4217,7 +4333,6 @@ async function generateAgentArtifacts(agents, rootDir, dist) {
|
|
|
4217
4333
|
const result = extractAgentMetadata(program, absPath, {
|
|
4218
4334
|
name: manifest.name,
|
|
4219
4335
|
filePath: manifest.filePath,
|
|
4220
|
-
hasConfig: manifest.hasConfig,
|
|
4221
4336
|
hasRun: manifest.hasRun
|
|
4222
4337
|
});
|
|
4223
4338
|
if (result) {
|
|
@@ -4435,10 +4550,8 @@ async function createAppBase(options) {
|
|
|
4435
4550
|
if (agents.length > 0) {
|
|
4436
4551
|
console.log(`- Loaded ${agents.length} agent(s):`);
|
|
4437
4552
|
for (const agent of agents) {
|
|
4438
|
-
const exports =
|
|
4439
|
-
|
|
4440
|
-
if (agent.hasRun) exports.push("run");
|
|
4441
|
-
console.log(` ${agent.name} [${exports.join("+")}] ${agent.filePath}`);
|
|
4553
|
+
const exports = agent.hasRun ? "run" : "-";
|
|
4554
|
+
console.log(` ${agent.name} [${exports}] ${agent.filePath}`);
|
|
4442
4555
|
}
|
|
4443
4556
|
}
|
|
4444
4557
|
if (config?.lifecycle?.onClose) {
|
|
@@ -4540,6 +4653,7 @@ async function createAppBase(options) {
|
|
|
4540
4653
|
}
|
|
4541
4654
|
clearToolRegistry();
|
|
4542
4655
|
clearAgentRegistry();
|
|
4656
|
+
clearSkillRegistry();
|
|
4543
4657
|
clearAgentHandleFactory();
|
|
4544
4658
|
if (!server.listening) {
|
|
4545
4659
|
app.server = null;
|
|
@@ -4831,7 +4945,6 @@ import fg4 from "fast-glob";
|
|
|
4831
4945
|
import path17 from "path";
|
|
4832
4946
|
import fs18 from "fs";
|
|
4833
4947
|
var DEFAULT_AGENT_PATTERNS = ["src/agents/*/handler.ts"];
|
|
4834
|
-
var CONFIG_EXPORT_RE = /export\s+(?:const|function)\s+config\b/;
|
|
4835
4948
|
var RUN_EXPORT_RE = /export\s+(?:async\s+)?(?:function\s+|const\s+)run\b/;
|
|
4836
4949
|
function extractAgentNameFromPath(filePath) {
|
|
4837
4950
|
const normalized = filePath.replace(/\\/g, "/");
|
|
@@ -4845,7 +4958,6 @@ function extractAgentNameFromPath(filePath) {
|
|
|
4845
4958
|
}
|
|
4846
4959
|
function detectAgentExports(source) {
|
|
4847
4960
|
return {
|
|
4848
|
-
hasConfig: CONFIG_EXPORT_RE.test(source),
|
|
4849
4961
|
hasRun: RUN_EXPORT_RE.test(source)
|
|
4850
4962
|
};
|
|
4851
4963
|
}
|
|
@@ -4865,7 +4977,7 @@ async function scanAgents(rootDir, patterns) {
|
|
|
4865
4977
|
}
|
|
4866
4978
|
const absPath = path17.resolve(rootDir, normalizedFile);
|
|
4867
4979
|
const source = await fs18.promises.readFile(absPath, "utf8").catch(() => "");
|
|
4868
|
-
const {
|
|
4980
|
+
const { hasRun } = detectAgentExports(source);
|
|
4869
4981
|
const name = extractAgentNameFromPath(normalizedFile);
|
|
4870
4982
|
const prevFile = seen.get(name);
|
|
4871
4983
|
if (prevFile) {
|
|
@@ -4877,7 +4989,6 @@ async function scanAgents(rootDir, patterns) {
|
|
|
4877
4989
|
agents.push({
|
|
4878
4990
|
name,
|
|
4879
4991
|
filePath: normalizedFile,
|
|
4880
|
-
hasConfig,
|
|
4881
4992
|
hasRun
|
|
4882
4993
|
});
|
|
4883
4994
|
}
|
|
@@ -4947,11 +5058,15 @@ export {
|
|
|
4947
5058
|
createProgram,
|
|
4948
5059
|
extractTypeInfo,
|
|
4949
5060
|
getAgent,
|
|
5061
|
+
getAgentEntry,
|
|
4950
5062
|
getApp,
|
|
4951
5063
|
getInputTypeForMethod,
|
|
5064
|
+
getSkill,
|
|
4952
5065
|
getTool,
|
|
4953
5066
|
helmet,
|
|
5067
|
+
hydrateSkillRegistry,
|
|
4954
5068
|
invalidateProgramCache,
|
|
5069
|
+
listSkills,
|
|
4955
5070
|
loadAgentModule,
|
|
4956
5071
|
loadConfig,
|
|
4957
5072
|
loadEnv,
|
|
@@ -4959,8 +5074,10 @@ export {
|
|
|
4959
5074
|
loadToolSchema,
|
|
4960
5075
|
logger,
|
|
4961
5076
|
registerAgentHandleFactory,
|
|
5077
|
+
removeSkill,
|
|
4962
5078
|
resolveAgentTools,
|
|
4963
5079
|
resolveSubAgents,
|
|
4964
|
-
resolveTypeNode
|
|
5080
|
+
resolveTypeNode,
|
|
5081
|
+
upsertSkill
|
|
4965
5082
|
};
|
|
4966
5083
|
//# sourceMappingURL=index.js.map
|